初始化类成员的有效方法;分配堆和堆栈

时间:2013-03-07 01:11:18

标签: c++ constructor dynamic-memory-allocation

假设我们有以下内容:

//! 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成员的create unnecessary copies,因此只是为了分配内存而分配然后释放内存。 / p>


通常使用初始化列表,再加上单独的初始化函数(用于堆分配的内存)作为避免这种情况的方法。假设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* mBarFoo* mFoo替换为:

std::unique_ptr< Foo > mFoo;
std::unique_ptr< Bar > mBar;

然后我们可以这样做,

SomeClass::SomeClass( void )
   mFoo( new Foo ),
   mBar( new Bar ),
   mStackFoo( ... )
{
}

由于智能指针在自己的构造函数中有效包装内存分配。

虽然这是一个很好的解决方案,但我个人不是一个为我创建的每个堆对象使用智能指针的人,而且我知道C ++社区中有其他人也有同样的感觉。


TL;博士

尽管如此,我真正想知道的是如果有更高效的替代方法来初始化对象中的类成员(特别是随着C ++ 11的出现) ),除了我上面列出的那些。

2 个答案:

答案 0 :(得分:4)

unique_ptr是正确的解决方案。它有几个优点:

  • 明确记录所有权。原始指针的一个问题是它们没有表明谁拥有它们。使用unique_ptr您拥有一个所有者,并且如果您想要转让,则必须明确move所有权。

  • 它基本上没有开销; unique_ptr唯一能做的就是调用删除器,无论如何你都要这样做。现在,无需手动内存管理,您就可以获得确定性内存行为的性能优势。

  • 由于RAII,它使线程和异常安全性更容易实现。这意味着更少关于指令顺序的烦恼,以及不那么明确的清理代码。您可以获得异常的好处,而不会导致在如此多的C ++ 03代码中避免所有问题。

根据我的经验,

shared_ptrunique_ptr所需的频率低得多。共享所有权主要用于具有不可变资源(如纹理或音频文件),其中加载和复制都很昂贵,但是在不使用时要卸载的资源。 shared_ptr还增加了安全性(特别是线程安全性)和引用计数的开销。

当然,缺点是智能指针会产生语法开销。它们不像原始指针那样“原生”。为此,您拥有typedefautodecltype,并推出自己的便利功能,例如make_unique

答案 1 :(得分:3)

你为什么不能这样做?

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;
}

如果exception is thrown inside the constructor itself只考虑异常,则不会仅调用SomeClass的析构函数。

最后,关于线程是否安全,它取决于每个线程是否有自己的SomeClass副本以及SomeClass是否包含正在写入的静态成员。