在下面的代码中,我定义了4个类:
A
.B_parent
。B_parent
类B_child
,其中包含对类A
的对象的引用,该引用由构造函数初始化。C
,有两个引用:类A
的对象和类B_parent
的对象,引用可以由两个构造函数初始化。类
C
中的第一个构造函数工作得很好(尽管在 main()
中,您可以看到我使用派生类对象初始化它)。
类
C
中的第二个构造函数导致悬空引用,并且代码因分段错误而崩溃。
为什么在第二种情况下引用会损坏?如何修复此错误?
我的目标是为类
_b
中的引用C
提供默认值,如果在构造类B_parent
的对象时未提供C
对象。
代码:
#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;
}
我尝试重新排序初始化列表中的变量,尝试替换类中的第二个构造函数
C
:
C(const A &a) : _a(a), _b(B_child(a)) {}
与
C(const A &a) : _a(a), _b(B_child(_a)) {}
仍然出现分段错误。
编辑:
我设法使用指针解决了这个问题。类
C
可以重写为:
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();
}
};
但是,这看起来不是一个好的做法,因为我使用
new B_child(a)
创建了一个默认对象,但从未销毁它。我应该在析构函数中删除这个指针还是有更好的方法在类中提供默认对象C
?
首先我会解释一下你原来的代码哪里出了问题。
C(const A &a) : _a(a), _b(B_child(a)) {}
这会创建一个类型为
B_child
的临时对象,获取对此对象的引用并将其存储在 _b
中。然后 B_child
被删除,因为它是临时的,并且 _b
中的引用悬空。
另一个构造函数,
C(const A &a, const B_parent &b)
,工作得很好,因为它引用了一个位于构造函数调用之外的对象。
这里的基本考虑是:你的对象住在哪里?对于工作构造函数,这是由调用者管理的。可能存在悬空引用,但前提是调用者搞砸了。对于损坏的构造函数,B 对象没有地方可以生存,因此引用始终处于悬空状态。
现在来说说如何解决这个问题。这很大程度上取决于您想要做什么。
如果您需要能够使用虚拟方法存储基类,那么您应该使用智能指针(
unique_ptr
或 shared_ptr
)。这将为您处理引用计数和删除。
如果您需要能够引用现有的
B_parent
或创建一个新的,具体取决于调用的构造函数,您还应该使用智能指针。在这种情况下,shared_ptr
是您唯一的选择,因为unique_ptr
无法引用其他地方也保存的内容。
否则,如果您不需要这些功能,
_b
应该只是一个值而不是引用/指针。