Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding a string-type class member causes base class function to be called instead of child

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.
like image 930
Hydra Avatar asked Jan 19 '26 03:01

Hydra


1 Answers

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

like image 104
user4581301 Avatar answered Jan 21 '26 19:01

user4581301



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!