如何在构造函数中处理'this'指针?

时间:2010-03-24 18:47:17

标签: c++ boost shared-ptr constructor

我有在其构造函数中创建其他子对象的对象,传递'this'以便子节点可以将指针保存回其父节点。我在编程中广泛使用boost :: shared_ptr作为std :: auto_ptr或原始指针的更安全的替代品。所以孩子会有shared_ptr<Parent>这样的代码,而boost会提供父母可以给孩子的shared_from_this()方法。

我的问题是shared_from_this()不能在构造函数中使用,这不是真正的犯罪,因为“this”不应该在构造函数中使用,除非你知道你在做什么而不是注意局限性。

Google的C ++样式指南states构造函数应该只将成员变量设置为其初始值。任何复杂的初始化都应该采用显式的Init()方法。这解决了“这个构造函数”问题以及其他一些问题。

困扰我的是,现在使用你的代码的人必须记住每次构造你的一个对象时都调用Init()。我可以想到强制执行此操作的唯一方法是通过断言已经在每个成员函数的顶部调用了Init(),但这样编写繁琐且执行起来很麻烦。

那里有任何习惯用法可以解决这个问题吗?

6 个答案:

答案 0 :(得分:16)

使用工厂方法进行两阶段构建&amp;初始化你的课程,然后制作你的课程。 Init()函数私有。然后没有办法错误地创建您的对象。只需记住将析构函数保持公开并使用智能指针:

#include <memory>

class BigObject
{
public:
    static std::tr1::shared_ptr<BigObject> Create(int someParam)
    {
        std::tr1::shared_ptr<BigObject> ret(new BigObject(someParam));
        ret->Init();
        return ret;
    }

private:
    bool Init()
    {
        // do something to init
        return true;
    }

    BigObject(int para)
    {
    }

    BigObject() {}

};


int main()
{
    std::tr1::shared_ptr<BigObject> obj = BigObject::Create(42);
    return 0;
}

修改

如果您想要反对堆叠,您可以使用上述模式的变体。如上所述,这将创建一个临时的并使用copy ctor:

#include <memory>

class StackObject
{
public:
    StackObject(const StackObject& rhs)
        : n_(rhs.n_)
    {
    }

    static StackObject Create(int val)
    {
        StackObject ret(val);
        ret.Init();
        return ret;
    }
private:
    int n_;
    StackObject(int n = 0) : n_(n) {};
    bool Init() { return true; }
};

int main()
{
    StackObject sObj = StackObject::Create(42);
    return 0;
}

答案 1 :(得分:8)

谷歌的C ++编程指南在这里和其他地方一再被批评。这是正确的。

我只使用两阶段初始化,如果它隐藏在包装类后面。如果手动调用初始化函数会起作用,我们仍然会用C和C ++编程,它的构造函数永远不会被发明。

答案 2 :(得分:5)

根据具体情况,这可能是共享指针不添加任何内容的情况。任何时候终身管理都是一个问题,应该使用它们。如果保证子对象的生命周期短于父对象的生命周期,我认为使用原始指针没有问题。例如,如果父对象创建并删除子对象(并且没有其他对象),则不应该删除谁应该删除子对象。

答案 3 :(得分:3)

KeithB有一个非常好的观点,我想扩展(在某种意义上与问题无关,但这不符合评论):

在对象与其子对象关系的特定情况下,生命周期得到保证:父对象将始终比子对象更长。在这种情况下,子(成员)对象共享父(包含)对象的所有权,并且不应使用shared_ptr。它不应该用于语义原因(根本没有共享所有权),也不应该用于实际原因:你可以引入各种各样的问题:内存泄漏和错误的删除。

为了便于讨论,我将使用P来引用父对象,使用C来引用子对象或包含对象。

如果使用P从外部处理shared_ptr生命周期,则在shared_ptr中添加另一个C以引用P将会产生周期。一旦你通过引用计数管理内存循环,你很可能有内存泄漏:当引用shared_ptr的最后一个外部P超出范围时,C中的指针是仍然存活,因此P的引用计数未达到0且对象未被释放,即使它不再可访问。

如果P由不同的指针处理,那么当指针被删除时,它将调用P析构函数,它将级联到调用C析构函数中。 P shared_ptrC的{​​{1}}的引用计数将达到0,并且会触发双重删除。

如果P具有自动存储持续时间,当它的析构函数被调用(对象超出范围或调用包含对象析构函数)时,shared_ptr将触发删除内存块那不是新的。

常见的解决方案是使用weak_ptr s打破周期,这样子对象就不会将shared_ptr保留给父对象,而是weak_ptr。在这个阶段,问题是相同的:创建weak_ptr对象必须已经由shared_ptr管理,在构造期间不能发生。

考虑使用原始指针(通过指针处理资源的所有权是不安全的,但这里所有权是外部处理的,因此这不是问题)或甚至是引用(这也告诉其他程序员你信任引用对象P比引用对象C

更长久

答案 4 :(得分:0)

需要复杂构造的对象听起来像是工厂的工作。

定义一个接口或一个抽象类,一个无法构造的类,加上一个可能带有参数的自由函数,它返回一个指向接口的指针,但是在幕后,它会处理复杂性。

您必须根据班级最终用户的要求来考虑设计。

答案 5 :(得分:0)

在这种情况下你真的需要使用shared_ptr吗?孩子可以指针吗?毕竟,它是子对象,所以它由父对象拥有,所以它不能只是一个指向它的父对象的正常指针吗?