堆分配成员引用是一个可怕的想法?为什么?

时间:2011-08-23 20:55:49

标签: c++ memory reference heap

为了最好地解释这个问题,我构建了一个简单的例子。假设我有一个课程Blob,如下所示:

class Blob
{
    string personalName;
    string& familyName;
}

创建者(又名程序员)可以生成Blob,此时它可以选择personalName,因为它有权成为第一代Blob ,它可以选择它自己的familyName

或者,可以通过生成现有的Blob来创建Blob,此时它会选择自己的personalName,但与所有其他familyName共享Blob {1}}已被克隆在这个家庭中。如果一个Blob更改了姓氏,则所有其他家庭成员都会自动更改该名称。

到目前为止,这听起来一切都很好,直到编写Blob构造函数时我才看到:

Blob::Blob() :
    personalName(pickName()),
    familyName(pickFamilyName())
{ }

...

string& Blob::pickFamilyName()
{
    return *(new string("George"));
}   // All Blobs have family name "George" in this example

伊克!在堆上分配内存然后将其分配给引用变量?!这看起来很吓人!

我的直觉是否正确,这有什么不妥之处,或者它只是让我觉得奇怪,因为它不是一个常见的模式?如果出现问题,那是什么?为什么这是一个糟糕的设计?

注意:通过引用计数释放堆分配的内存并在最后一个Blob被破坏时删除内存或通过其他方法释放内存非常重要。

3 个答案:

答案 0 :(得分:14)

将引用存储为类成员的唯一时间是:

  1. 该类不属于该类,并且该类不负责释放它;
  2. 保证引用对象的生命周期长于包含对象的生命周期。
  3. 您的示例违反了规则1.

答案 1 :(得分:8)

我认为它的“臭”部分是将变量存储为字符串的引用,因为很难跟踪它是否是一个有效的对象。为什么不使用类似的东西:

boost::shared_ptr<std::string> Blob::pickFamilyName()
{
    return boost::shared_ptr<std::string>(new std::string("George"));
}

修改

根据Praetorian的建议,您可以避免自己手动分配内存:

boost::shared_ptr<std::string> Blob::pickFamilyName()
{
    return boost::make_shared<std::string>("George");
}

答案 2 :(得分:1)

一个参数是一致性:operator new返回一个指针,operator delete接受指针,因此预期用于引用动态分配的对象的类型也将是指针,而不是引用。这是一个严肃的论点:如果你是不一致的并且没有充分理由反对程序员的习惯,那么你就会混淆它们并滋生错误。通常没有人会期望一个函数返回一个引用来在堆上创建新对象,然后调用代码必须删除它,所以迟早会有人忘记这样做。

但是还有一些实用的原因,指针可以重新分配,并且可以将它们设置为null,这使得它们更便于处理动态分配的对象。在您的示例中,Blob类负责在引用成员上调用delete。您通常会在析构函数中执行此操作。但是想象一下你想要更快地释放内存:使用指针你可以在调用delete之后为它们分配null,然后让它们的析构函数再次安全地调用delete,使用引用成员你会留下一个你不能做的悬空引用任何事情。

更严重的问题是异常安全:如果Blob具有更长的初始化列表或非空体,则构造函数可能在调用pickFamilyName()之后抛出。在这种情况下,析构函数不会被调用,并且您有内存泄漏。理想情况下,您可以使用RAII,但是使用指针也可以在初始化列表中将指针指定为null,然后将其指向try / catch块中构造函数体中新创建的对象,以确保对象即使构造函数抛出并且没有析构函数调用,也会被删除。这也不能用参考文献来完成。