堆分配的成员引用是一个糟糕的想法吗?为什么
Are heap allocated member references a terrible idea? Why?
为了更好地解释这个问题,我构造了一个简单的示例。假设我有一个类Blob
,如下所示:
class Blob
{
string personalName;
string& familyName;
}
Blob
可以由Creator(又名程序员)生成,此时它可以选择personalName
,并且由于它具有第一代Blob
的特权,它可以选择自己的familyName
。
或者,可以通过衍生现有的Blob
来创建Blob
,此时它选择自己的personalName
,但与该家族中克隆的所有其他Blob
共享familyName
。如果一个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被销毁时,通过引用计数和删除内存来释放堆分配的内存是很重要的,或者通过其他方法。
将引用存储为类成员的唯一有意义的情况是:
- 数据不属于类,类不负责释放数据;
- 被引用对象的生存期保证比包含对象的生存期长。
我认为它的"臭味"部分是将变量存储为对字符串的引用,因为很难跟踪它是否为有效对象。为什么不使用如下格式:
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");
}
一个参数将是一致性:operator new
返回一个指针,而operator delete
接受一个指针,因此预计用于引用动态分配对象的类型也将是指针,而不是引用。这是一个严肃的争论:如果你不一致并且毫无理由地违背程序员的习惯,你就会混淆他们并滋生bug。通常没有人会期望一个返回引用的函数在堆上创建新的对象,然后调用代码必须删除这些对象,所以迟早有人会忘记这样做。
但是也有一些实用的原因,指针可以被重新分配,它们可以被设置为null,这使得它们更方便处理动态分配的对象。在您的示例中,Blob类负责对引用成员调用delete。你通常会在析构函数中这样做。但是想象一下,你想更快地释放内存:对于指针,你可以在调用delete后给它们赋值null,然后让它们的析构函数安全地再次调用delete,对于引用成员,你就留下了一个悬空的引用,你不能做任何事情。
更严重的问题是异常安全:如果Blob具有较长的初始化列表或非空体,则构造函数可能在调用pickFamilyName()后抛出异常。在这种情况下,析构函数不会被调用,从而导致内存泄漏。理想情况下,您可以使用RAII来实现这一点,但是对于指针,也可以在初始化列表中将指针赋值为null,然后在try/catch块中将其指向构造函数体中新创建的对象,这样即使构造函数抛出并且没有析构函数调用,也可以确保对象被删除。这同样不能用引用来实现。
- 为什么一个向量上的多线程操作很慢
- 为什么一个进程会挂在RtlExitUserProcess/LdrpDrainWorkQueue中?
- 为什么一个简单的程序不能立即启动
- 为什么一个变量获得与另一个值相同的值
- 当类型适当的构造函数可用时,为什么一个编译器尝试使用已删除的副本构造函数
- c++:为什么一个特定的参数必须由lambda中的值捕获
- 为什么一个二维阵列会导致赛段故障,而另一个则不会导致?
- 为什么一个表达式中的 std::string 连接给出的结果与逐个字符不同的结果?
- 为什么一个接一个声明的两个变量在内存中不相邻?
- 为什么一个方法对同一个变量有常量和非常量参数?
- 为什么一个简单的"Hello World"风格的程序不能用Turbo C++编译?
- 为什么一个简单的C++程序会有错误的结果?是 #define 引起的吗?
- 为什么这些直方图功能有所不同,为什么一个非确定性
- 为什么一个非平凡的成员需要为同一类中的匿名联合定义构造函数
- 为什么一个"hello world"的 c++ 程序会在 Visual Studio 2015 中引发如此多的"iosfwd"错误?
- 为什么一个天真的'iter_swap`电位比``swap''慢得多
- 为什么一个空循环要使用这么多GPU(OpenGL)
- 为什么一个具有多个公共访问说明器标准_layout的类
- 为什么一个成员函数只存在一次,甚至在一个包含多个的.h文件中定义?
- 为什么一个omp线程完成,而另一个却不能完成