3

Why does the following code print 0, but if you comment out "std::string my_string" it prints 1?

#include <stdio.h>
#include <iostream>

class A {
  public:
    virtual int foo() {
      return 0;
    }
  private:
    std::string my_string;
};

class B : public A {
  public:
    int foo() {
      return 1;
    }
};

int main()
{
    A* a;
    if (true) {
      B b;
      a = &b;
    }
    std::cout << a->foo() << std::endl;
    return 0;
}

I also understand that changing std::string to std:string* also causes the code to print 1, as does removing the if-statement, though I don't understand why any of that is true.

EDIT: This seems to be due to a dangling pointer. Then what's the standard pattern in C++ to do something like this in Java:

Animal animal; 
boolean isDog = false; 
// get user input to set isDog 
if (isDog) { 
  animal = new Dog();
} else {
  animal = new Cat();
}
animal.makeNoise(); // Should make a Dog/Cat noise depending on value of isDog.
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Hydra
  • 33
  • 3
  • 1
    Undefined behavior. You're trying to use a local variable that has gone out of scope. – Mark Ransom Oct 17 '19 at 22:27
  • Interesting. Then what's the standard pattern in C++ to do something like this in Java: Animal animal; boolean isDog = false; // get user input to set isDog if (isDog) { animal = new Dog(); } else { animal = new Cat(); } – Hydra Oct 17 '19 at 22:32
  • @Hydra 1) If you used `new` - it wouldn't be a dangling pointer, and it would've worked fine. Note: you would be required to `delete` it, though. Hence, your comparison with Java code is not equivalent (even if it is similar). it's like asking why apples taste different to oranges, if they are both round. 2) C++ is not Java. – Algirdas Preidžius Oct 17 '19 at 22:33
  • @AlgirdasPreidžius If you post an answer with a corrected version of my code using new then I will accept it as the correct answer. Sorry I am not sure where I would delete the pointer if it only exists within the if scope. – Hydra Oct 17 '19 at 22:39
  • @Hydra 1) There already was an answer, explaining the issue, at the time of me writing the comment. I was merely responding to your Java snippet, not being equivalent to your C++ snippet. 2) What do you mean by "_Sorry I am not sure where I would delete the pointer if it only exists within the if scope._"? It doesn't. Such a question suggests, that you are not familiar with C++ language fundamentals (such as: object lifetime), and you should learn them from a [good C++ book](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) instead. – Algirdas Preidžius Oct 17 '19 at 22:49

1 Answers1

5

Problem

The program has Undefined Behaviour. b is only in scope inside the body of the if. You can't count on logical results when accessing a dangling pointer.

int main()
{
    A* a;
    if (true) {
      B b; // b is scoped by the body of the if.
      a = &b;
    } // b's dead, Jim.
    std::cout << a->foo() << std::endl; // a points to the dead b, an invalid object
    return 0;
}

TL;DR Solution

int main()
{
    std::unique_ptr<A> a; // All hail the smart pointer overlords!
    if (true) {
      a = std::make_unique<B>();
    }
    std::cout << a->foo() << std::endl;
    return 0;
} // a is destroyed here and takes the B with it. 

Explanation

You can point a at an object with a dynamic lifetime

int main()
{
    A* a;
    if (true) {
      a = new B; // dynamic allocation 
    } // b's dead, Jim.
    std::cout << a->foo() << std::endl; 
    delete a; // DaANGER! DANGER!
    return 0;
}

Unfortunately delete a; is also undefined behaviour because A has a non-virtual destructor. Without a virtual destructor the object pointed at by a will be destroyed as an A, not as a B.

The fix for that is to give A a virtual destructor to allow it to destroy the correct instance.

class A {
  public:
    virtual ~A() = default;
    virtual int foo() {
      return 0;
    }
  private:
    std::string my_string;
};

There is no need to modify B because once a function is declared virtual, it stays virtual for its children. Keep an eye out for final.

But it's best to avoid raw dynamic allocations, so there is one more improvement we can make: Use Smart pointers.

And that brings us back to the solution.

Documentation for std::unique_ptr

Documentation for std::make_unique

user4581301
  • 33,082
  • 7
  • 33
  • 54