初始化类成员的有效方法;堆和堆栈都已分配
Efficient ways to initialize class members; both heap and stack allocated
假设我们有以下内容:
//! SomeClass.hpp
class
{
public:
SomeClass( void );
~SomeClass( void )
{
delete mFoo;
delete mBar;
}
...
private:
Foo* mFoo;
Bar* mBar;
StackObj mStackFoo;
};
//! SomeClass.cpp
SomeClass::SomeClass( void )
{
mFoo = new Foo;
mBar = new Bar;
mStackFoo = StackObj( ... );
}
现在,当我们初始化指针时,我的理解是构造函数将创建一些不必要的SomeClass成员的副本,从而分配然后仅仅为了分配内存而释放内存。
通常使用初始化器列表,加上单独的初始化函数(用于堆分配内存)作为避免这种情况的方法。假设SomeClass
有一个私有成员函数定义为void initHeapMem( void )
。然后我们可以,
SomeClass::SomeClass( void )
: mFoo( NULL ),
mBar( NULL ),
mStackFoo( ... )
{
initHeapMem();
}
void SomeClass::initHeapMem( void )
{
mFoo = new Foo;
mBar = new Bar;
}
很自然地,这个在一定程度上解决了这个问题。我认为这里的问题是,仍然存在执行另一个函数调用的开销。
我们不能对原始指针使用初始化列表的原因是它们不是线程安全的。如果出现问题,程序抛出异常,仍然会有内存泄漏。注意:这是根据我所读到的,如果这是错误的,我的道歉
因此,在boost/c++ 11中,我们可以在头文件中使用#include <tr1/memory>
指令中的智能指针(假设我们使用STL)。
如果我们使用std::unique_ptr< T >
,那么我们将Bar* mBar
和Foo* mFoo
替换为:
std::unique_ptr< Foo > mFoo;
std::unique_ptr< Bar > mBar;
这将允许我们做,
SomeClass::SomeClass( void )
mFoo( new Foo ),
mBar( new Bar ),
mStackFoo( ... )
{
}
由于智能指针有效地将的内存分配包装在自己的构造函数中。 虽然这是一个很好的解决方案,但我个人并不是一个为我创建的每个堆对象使用智能指针的人,我知道在c++社区中有其他人也有同样的感觉。 有了所有这些,我真正想知道的是是否有更有效的替代方法来初始化对象中的类成员(特别是随着c++ 11的出现),除了我上面列出的那些。 tl;博士
unique_ptr
是正确的解决方案。它有几个优点:
-
它明确地记录了所有权。原始指针的一个问题是,它们没有表明任何关于谁拥有它们的信息。对于
unique_ptr
,你有一个单一的所有者,如果你想转移它,必须明确地move
所有权。 -
它基本上没有开销;
unique_ptr
所做的唯一一件事就是调用删除器,无论如何您都要这样做。现在,您无需手动内存管理就可以获得确定性内存行为的性能优势。 -
多亏了RAII,线程和异常安全更容易实现。这意味着更少的指令顺序和更少的显式清理代码。您可以获得异常的好处,而不会出现在c++ 03代码中导致避免异常的所有问题。
shared_ptr
在我的经验中比unique_ptr
需要的少得多。共享所有权主要在拥有不可变资源(如纹理或音频文件)时非常有用,因为这些资源的加载和复制都很昂贵,但您希望在不使用时卸载它们。shared_ptr
还增加了安全性(特别是线程安全性)和引用计数的开销。
typedef
、auto
、decltype
和滚动您自己的方便函数,如make_unique
。
你为什么不能这么做?
SomeClass::SomeClass( void ) :
mFoo(new Foo)
, mBar(new Bar)
{
}
它们是原始指针,不会创建不必要的副本。
我还应该指出,使用初始化列表的原因是,在执行构造函数体时,对象处于有效状态(即所有成员都具有有效值)。
SomeClass::SomeClass( void )
{
//before this point, mFoo and mBar's values are unpredictable
mFoo = new Foo;
mBar = new Bar;
}
对于异常,只有在构造函数内部抛出异常时,才会调用SomeClass的析构函数。
最后,关于线程安全与否,它取决于每个线程是否有自己的someeclass副本,以及someeclass是否包含正在写入的静态成员。
- 从堆栈分配的原始指针构造智能指针
- 在什么情况下,两个堆栈分配的结构对象的 this 点指向同一个地址?
- 如何模板化堆栈分配的多态指针数组到接口,包括派生类型的相应点?
- 堆栈分配的类类型.为什么两个 ID 实例的地址相同?
- C++析构函数调用两次,堆栈分配的复合对象
- 了解通过引用传递取消引用指针时C++堆/堆栈分配
- C++,在对象内分配多个数据时,堆栈分配是否更有效? 在下面的程序中,类A_Heap的效率会更低吗?
- 何时在函数中声明堆栈分配变量?
- 使用 std::map 的递归堆栈分配如何工作?
- tcmalloc 与纯堆栈分配性能有多接近
- 安全分配堆栈分配的阵列
- C++ 中的黑白堆分配对象和堆栈分配对象的性能差异
- 跟踪(堆栈分配)对象
- 堆栈分配的确切时间
- 未初始化的值是由堆栈分配 - Qt - C++创建的
- 用于堆栈分配对象的C++虚拟析构函数内联
- C STD :: BAD_ALLOC来自堆栈分配
- 是使用COM创建的对象,将采用分配或堆栈分配的内存
- C++ 中针对大型的堆栈分配
- 堆栈分配的向量如何在 C++ 中扩展