将新对象放入构造函数初始化器中是否明智?

3

在构造函数初始化器中传递new的对象是明智还是愚蠢?

例如:

class Mallard
{
public:
  Mallard()
   : s(new SomeLargeObject(5)) {}
private:
  SomeLargeObject * s;
};

2
@patchwork 当然可以,但只有一个new的话,加入第二个动态分配就有可能会泄漏。即使只有一个动态分配成员,使用unique_ptrshared_ptr来管理仍然更好。 - Praetorian
3
为什么不呢?只需确保析构函数清理即可。 - Ed Heal
2
@patchwork 我假设你已经在析构函数中添加了相应的delete以防止明显的内存泄漏。但是,如果你初始化第二个成员,比如说 Mallard():s(new Foo()), s2(new Bar()) {} 并且第二个分配失败了,析构函数将不会被调用,从而导致s泄漏。 - Praetorian
1
@patchwork 当然不是。第一个分配和第二个有什么特别之处吗?请阅读此文。如果只有一个动态分配的成员,如果该分配失败,则无论是否调用析构函数都无关紧要,因为您没有分配任何内存,因此不需要 delete 它。我试图说明的重点是管理原始指针以及其他数据成员容易出错,请使用智能指针。请注意,所有这些都适用于您管理的任何类型的资源(文件、套接字等),而不仅仅是内存。 - Praetorian
4
如果构造函数抛出异常,相应的析构函数将不会被调用。因此,如果你有像 Mallard():s(new Foo()), s2(new Bar()) {} 这样的代码,且 s(new Foo()) 成功执行,然后 s2(new Bar()) 抛出异常,那么 ~Mallard() 就不会被调用,导致 s 被泄露。除非声明 s 为智能指针(如 auto_ptr<Foo> sunique_ptr<Foo> s),在这种情况下,智能指针的析构函数将会被调用(当构造函数抛出异常时,已经构造的成员就会被销毁),内存将按预期释放。 - Remy Lebeau
显示剩余12条评论
3个回答

5

如果您忘记发布析构函数并且该析构函数进行对称删除,则代码示例中没有机械损坏。

虽然传统做法(更安全,更少的样板代码)是希望有类似以下的内容:

#include <memory>

class Mallard {
    std::unique_ptr<SomeLargeObject> that_;
public:
     Mullard(): that_(new SomeLargeObject)
     {}
     // this way you don't need to code the destructor
};

注意: 正如评论中所指出的(@Praetorian,@RemyLebeau,@AdrianMcCarthy),如果您以这种方式初始化了多个成员,则不会受到内存泄漏的影响,因为当对象构造函数抛出异常时,该对象的析构函数未被调用。上述建议使用std::unique_ptr的语法不会受到此问题的影响( 这就是它安全的原因)。

2
值得指出的是,如果构造函数需要动态分配两个或更多对象,则可能会发生泄漏,此时使用智能指针就变得至关重要。 - Adrian McCarthy
@MooingDuck - 我不认为我有任何误解。您可以检查此代码的stdout,并在valgrind下运行它:http://ideone.com/cDiYVN - bobah
2
@MooingDuck - 我就放在这里:https://dev59.com/B3I-5IYBdhLWcg3w99oH#1589987,供两位匿名的投票者参考。 - bobah
1
@bobah:好吧,我看起来很愚蠢。我没有意识到每个成员初始化器都被视为完整表达式,因此是有序的,避免了潜在的内存泄漏。学到了新东西+1。 - Mooing Duck

1

如果我可以发表个人意见:

第一点:一般来说,将s作为SomeLargeObject类型的成员变量会更好,并且不必担心动态内存分配;

第二点:有时在构造函数中分配东西很有用,只要你小心,就没有问题。特别是对于像这样的桥接模式非常有用:

// Abstract base of all beaks
class Beak
{
  public:
};

// Abstract base of all birds
class Bird
{
  protected: 
    const Beak *beak;

    ~Bird()
    {
        delete beak;
    }
};

class MallardBeak : public Beak
{
};

class Mallard : public Bird
{
  public:
    Mallard() : beak(new MallardBeak) { }
};

class PigeonBeak : public Beak
{
};

class Pigeon : public Bird
{
  public:
    Pigeon() : beak(new PigeonBeak) { }
};

0
由于C++没有像Java一样的垃圾回收器,因此通常在使用动态(堆上)创建的对象时必须非常小心。如果您错过了正确释放在某个时刻动态分配的内存,那么最终会导致内存泄漏。
此外,在构造函数的情况下,如果在对象创建时发生错误(例如引发异常),但是动态对象在异常调用之前创建了呢?这只是一些思考的食物,你知道的。
再次强调,要非常小心,并检查一些技术,以保护您免受此类错误和不良情况的影响,例如智能指针,它使用RAII习语。(Boost库集合有一些出色的实现,为您提供了各种智能指针,可以根据您的需求使用)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接