0

I have an abstract class Base and derived class Derived:

class Base
{
public:
    Base(int n) :_n(n) { _arr = new int[n]; }
    virtual ~Base() { delete[] _arr; }
    Base(Base&& other) { _n = other._n; _arr = other._arr; other._arr = nullptr; other._n = 0; }
    virtual void func() = 0;

private:
    int _n;
    int* _arr;
};

class Derived : public Base
{
public:
    Derived(int m, int n) : Base(n), _m(m) { _arr = new int[m]; }
    ~Derived() { delete[] _arr; }
    Derived(Derived&& other) : Base(std::move(other)) { _m = other._m; _arr = other._arr; other._arr = nullptr; other._m = 0; }
    void func() override { cout << "func"; }
private:
    int _m;
    int* _arr;
};

Then I have a class Bag which contains a rvalue reference of Base:

class Bag
{
public:
    Bag(Base&& b) : _b(std::move(b)) {}
    void func() { _b.func(); }
private:
    Base&& _b;
};

int main()
{
    Bag bag(Derived(1, 1));
    bag.func();
}

I use a rvalue reference member _b because I just want to take a temporary object in Bag's constructor. However, after bag.func() I got an error: abort() is called. It seems that bag._b's type changes from Derived to Base after the temporary object Derived(1,1) is destructed.

bag.func() works after I delete virtual ~Base() { delete[] _arr; }. Why? If I need Base's destructor, how can I change my code?

Ryime
  • 13
  • 2
  • 1
    Why do both `Base` and `Derived` have their own arrays? Why do you use pointers, `new[]` and `delete[]` instead of a simple `std::vector` (which means you could follow the rule of zero instead of the rule of three/five which you don't follow now)? – Some programmer dude Nov 18 '22 at 12:38
  • 5
    `Bag(Base&& b) : _b(std::move(b)) {}` creates a dangling reference to `Derived(1, 1)`. Life-time extension is not transitive. At the end of the full expression `Bag bag(Derived(1, 1));` the temporary is destroyed. – Richard Critten Nov 18 '22 at 12:41
  • 1
    After the temporary object is destroyed, any attempts to use it has undefined behaviour. – molbdnilo Nov 18 '22 at 12:43
  • @Someprogrammerdude `_arr` is not necessary. I use `_arr` just for destructor's necessity. – Ryime Nov 18 '22 at 12:49
  • Dangling reference. Use after delete. "`bag.func()` works..." is a bold claim. – Eljay Nov 18 '22 at 12:50
  • 1
    why do yuo want a `Base&&` member in `Bag` ? Whats the actualy aim? There might be a different solution – 463035818_is_not_an_ai Nov 18 '22 at 12:55
  • 1
    This seems to be very much an [XY problem](https://xyproblem.info/). Why do you need a `Base&&` member? What actual problem is that rvalue reference supposed to solve? – Some programmer dude Nov 18 '22 at 12:57
  • The copy constructor of `Base` is deleted in my scenario. My aim is to store a temporary `Base` object in `Bag`'s member. – Ryime Nov 18 '22 at 13:17
  • @Ryime never going to work. You get 1 life-time extension, to the constructor's parameter, which is not needed as the lifetime of the parameter and `Derived(1, 1)` is the same. Life-time extension can not be passed on, so when the full expression ends `Derived(1, 1)` is destroyed. – Richard Critten Nov 18 '22 at 13:19
  • 1
    Storing a *temporary* object is a contradiction in terms. – molbdnilo Nov 18 '22 at 13:26

1 Answers1

3

Base&& _b is a reference to the temporary object, when that temporary is destroyed at the end of the line Bag bag(Derived(1, 1)); the reference becomes a dangling reference and any use of the reference is undefined behaviour.

You could change _b to a value instead but that would slice your object.

If you want to store a polymorphic object the only real option is to use a pointer (ideally a smart one). For example:

class Bag
{
public:
    Bag(std::unique_ptr<Base>&& b) : _b(std::move(b)) {}
    void func() { _b->func(); }
private:
    std::unique_ptr<Base> _b;
};

int main()
{
    Bag bag(std::make_unique<Derived>(1, 1));
    bag.func();
}

Note that you should also ensure that Base and Derived follow the rule of five otherwise you may encounter issues when you assign objects.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60