Reputation: 23
In the code below, I defined 4 classes:
A
.B_parent
.B_parent
class B_child
which contains a reference to an object of class A
, the reference is initialized by the constructor.C
, with two references: object of class A
and object of class B_parent
, the references can be initialized by two constructors.The first constructor in class C
works perfectly fine (although, in the main()
, you can see I initialize it with the derived class object).
The second constructor in class C
leads to a dangling reference, and the code crashes with Segmentation fault.
Why is the reference broken in the second case and how can I fix this error?
My goal is to provide a default value for the reference _b
in class C
, if B_parent
object is not provided when I construct object of class C
.
The code:
#include <iostream>
using namespace std;
class A
{
public:
void foo() const
{
cout << "foo() from class A\n";
}
};
class B_parent
{
public:
virtual void foo() const
{
cout << "foo() from class B_parent\n";
}
};
class B_child : public B_parent
{
const A &_a;
public:
B_child(const A &a) : B_parent(), _a(a) {}
void foo() const
{
cout << "foo() from class B_child\n";
_a.foo();
}
};
class C
{
const A &_a;
const B_parent &_b;
public:
C(const A &a, const B_parent &b) : _a(a), _b(b) {}
C(const A &a) : _a(a), _b(B_child(a)) {}
void foo() const
{
cout << "foo() from class C\n";
_a.foo();
_b.foo();
}
};
int main()
{
A a;
B_child b(a);
cout << "Class C (variant 1):\n";
C c1(a, b); // Calling ctor with A and B_child objects -> OK
c1.foo();
cout << "\nClass C (variant 2):\n";
C c2(a); // Calling ctor with A and B_child(a) from class' init list -> Segmentation fault
c2.foo();
return 0;
}
I tried to reorder the variables in the init list, tried to replace the second constructor in class C
:
C(const A &a) : _a(a), _b(B_child(a)) {}
with
C(const A &a) : _a(a), _b(B_child(_a)) {}
Still get Segmentation fault.
EDIT:
I managed to solve this using a pointer. Class C
can be rewritten as:
class C
{
const A &_a;
const B_parent *_b;
public:
C(const A &a, const B_parent &b) : _a(a), _b(&b) {}
C(const A &a) : _a(a), _b(new B_child(a)) {}
void foo() const
{
cout << "foo() from class C\n";
_a.foo();
_b->foo();
}
};
However, it doesn't look like a good practice since I create a default object with new B_child(a)
but never destroy it. Should I just delete this pointer in the destructor or are there better ways of providing a default object in class C
?
Upvotes: 2
Views: 115
Reputation: 2964
First I'll explain where your original code goes wrong.
C(const A &a) : _a(a), _b(B_child(a)) {}
This creates a temporary object of type B_child
, takes a reference to this object and stores it in _b
. Then B_child
is deleted, because it was a temporary and the reference in _b
is left dangling.
The other constructor, C(const A &a, const B_parent &b)
, works fine because it takes a reference to an object that lives outside of the constructor call.
The basic consideration here is: where do your objects live? For the working constructor this is managed by the caller. There can be dangling references, but only if the caller screws up. For the broken constructor, the B object has no place to live, thus the reference is always dangling.
Now for how to solve this. It very much depends on what you're trying to do.
If you need the ability to store a base class with virtual methods, then you should use a smart pointer (unique_ptr
or shared_ptr
). This will take care of the reference counting and deletion for you.
If you need to be able to either reference an existing B_parent
or create a new one, depending on the constructor called, you should also use a smart pointer. In this case, shared_ptr
is your only option, because unique_ptr
can't reference something that is also held elsewhere.
Otherwise, if you need neither of those features, _b
should just be a value instead of a reference/pointer.
Upvotes: 2