-1

假设我们有以下内容:

//! 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 成员的不必要副本,因此只是为了分配内存而分配然后释放内存。


通常使用初始化列表,加上一个单独的初始化函数(用于堆分配的内存)作为避免这种情况的方法。SaySomeClass有一个私有成员函数,定义为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 的出现)。

4

2 回答 2

4

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

  • 它明确记录所有权。原始指针的问题之一是它们没有表明任何关于谁拥有它们的信息。unique_ptr您只有一个所有者,并且move如果您想转让它,则必须明确拥有所有权。

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

  • 多亏了 RAII,它使线程安全和异常安全更容易实现。这意味着减少对指令顺序的担忧,减少明确的清理代码。您可以获得异常的好处,而不会出现在如此多的 C++03 代码中导致它们被避免的所有问题。

shared_ptr根据我的经验,需要的频率远低于unique_ptr. 共享所有权主要在您拥有诸如纹理或音频文件之类的不可变资源时很有用,其中加载和复制都很昂贵,但您希望在不使用时将其卸载。shared_ptr还强加了额外的安全性(特别是线程安全性)和引用计数的开销。

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

于 2013-03-07T01:24:01.623 回答
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;
}

关于异常, SomeClass 的析构函数只有在构造函数本身抛出异常时才会被调用。

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

于 2013-03-07T01:17:55.227 回答