从构造函数中调用 shared_from_this()。

31

我必须在对象创建时将其注册到容器中。 如果没有智能指针,我会使用类似于以下内容的代码:

a_class::a_class()
{
    register_somewhere(this);
}

使用智能指针时,我应该使用shared_from_this,但是我无法在构造函数中使用它。

有没有一种简洁的方法来解决这个问题?在类似的情况下,您会怎么做?我正在考虑引入一个init方法,在创建后立即调用并将所有内容放置在像这样的工厂函数中:

boost::shared_ptr<a_class> create_a()
{
    boost::shared_ptr<a_class> ptr(new a_class);
    ptr->init();
    return ptr;
}
这样做可以吗?还是说有标准的程序需要遵循?
编辑:实际上我的情况更复杂。我有两个对象需要相互维护指针。所以事实上,我不是在“注册”,而是创建另一个对象(比如b_class),它需要this作为参数。b_class以弱指针的形式接收this并将其存储。
我添加了这段内容,因为你给我提供了设计建议(非常感激),至少你可以知道我在做什么:
a_class::a_class()
{
    b = new b_class(this);
}
在我的程序中,a_class是一个实体,而b_class是代表状态的具体类之一(在构造函数中它只是起始状态)。a_class需要一个指向当前状态的指针,而b_class需要操纵这个实体。 a_class负责创建和销毁 b_class 实例,因此维护一个shared_ptr指向它们,但b_class需要操纵a_class,因此维护一个弱指针。 a_class实例“存活”于b_class实例之上。
你建议在这种情况下避免使用智能指针吗?

1
我看不到你的代码足够多,但是显而易见的问题是为什么register_somewhere需要一个智能指针?通常情况下,如果一个对象不管理自己的生命周期(也就是假设或要求它将由智能指针持有),会更加清晰简洁。创建该对象的部分应该决定分配策略,静态、自动、由智能指针管理的动态分配等等。如果你能够将注册和所有权分开,那就更好了。 - CB Bailey
1
简单解决方案:sourceforge.net/projects/shared-from-this-ctor/ - Jakob Riedle
7个回答

8

a_class负责创建和销毁b_class实例。

...

a_class实例“存活”于b_class实例之上。

由于a_class实例负责销毁b_class实例,因此在a_class实例被销毁后,不存在b_class实例试图访问a_class实例的危险。

b_class可以仅持有指向其关联的a_class实例的指针。原始指针不表达任何所有权,在这种情况下是合适的。

在这个例子中,a_class是如何创建的并不重要,可以是动态创建的、作为聚合对象的一部分等等。无论如何创建a_class,都应由创建者管理其生命周期,就像a_class管理其实例化的b_class的生命周期一样。

例如:

class a_class;

class b_class
{
public:
    b_class( a_class* a_ ) : a( a_ ) {}
private:
    a_class* a;
};

class a_class
{
public:
    a_class() : b( new b_class(this) ) {}
private:
    boost::shared_ptr<b_class> b;
};

注意,在这个玩具示例中,不需要使用shared_ptr,一个对象成员同样可以工作(假设您没有复制实体类)。

class a_class
{
public:
    a_class() : b( this ) {}
private:
    b_class b;
};

5
如果在构造期间绝对需要使用shared_ptr,最好有一个“init”函数。实际上,这是我能想到的唯一合理的方法。如果您选择这条路,您应该可能有一个特殊的函数来创建此类型的对象,以确保调用'init()'。
但是,根据您要注册的内容,将对象注册到构造函数中的普通指针可能是更好的选择,而不是使用shared_ptr。然后,在析构函数中,您可以简单地从管理器中注销对象。

如果管理器是异步的,则注销并不保证在您的对象被销毁一毫秒后不会调用回调函数。共享指针和弱指针的组合可以解决这种竞争问题。 - SnakE

1
我为这个问题想出了一个帮助类:

template <class Impl>
class ImmediatelySharedFromThis : public std::enable_shared_from_this<Impl> {
    typedef std::unique_ptr<void, std::function<void(void*)>> MallocGuard;
    typedef std::shared_ptr<Impl> SharedPtr;

    // disallow `new MyClass(...)`
    static void *operator new(size_t) = delete;
    static void *operator new[](size_t) = delete;
    static void operator delete[](void*) = delete;
protected:
    typedef std::pair<MallocGuard&, SharedPtr&> SharingCookie;

    ImmediatelySharedFromThis(SharingCookie cookie) {
        MallocGuard &ptr = cookie.first;
        SharedPtr &shared = cookie.second;
        // This single line contains the actual logic:
        shared.reset(reinterpret_cast<Impl*>(ptr.release()));
    }
public:
    // Create new instance and return a shared pointer to it.
    template <class ...Args>
    static SharedPtr create(Args &&...args) {
        // Make sure that the memory is free'd if ImmediatelySharedFromThis
        // is not the first base class, and the initialization
        // of another base class throws an exception.
        MallocGuard ptr(aligned_alloc(alignof(Impl), sizeof(Impl)), free);
        if (!ptr) {
            throw std::runtime_error("OOM");
        }

        SharedPtr result;
        ::new (ptr.get()) Impl(SharingCookie(ptr, result),
                               std::forward<Args>(args)...);
        return result;
    }

    static void operator delete(void *ptr) {
        free(ptr);
    }
};

class MyClass : public ImmediatelySharedFromThis<MyClass> {
    friend class ImmediatelySharedFromThis<MyClass>;

    MyClass(SharingCookie cookie, int some, int arguments) :
        ImmediatelySharedFromThis(cookie)
        // You can pass shared_from_this() to other base classes
    {
        // and you can use shared_from_this() in here, too.
    }
public:
    ....
};

...

std::shared_ptr<MyClass> obj = MyClass::create(47, 11); 

有点丑,但是能用。

1
你的代码中没有必要使用 shared_ptr(就像你展示和解释的那样)。shared_ptr 适用于共享所有权的情况。
你的 b_class 并不拥有它的 a_class,事实上,后者甚至比前者存在时间更长,因此应该仅保留一个观察指针。
如果 b_class 是多态的,并且对 a_class 的操作涉及改变其 b_class 指针,则应使用 unique_ptr<b_class>
class a_class;
class b_class
{
  friend class a_class;
  a_class* mya;
  b_class(a_class*p)
  : mya(p) {}
public:
  virtual~b_class() {}   // required for unique_ptr<b_class> to work
  virtual void fiddle(); // do something to mya
};

class a_class
{
  std::unique_ptr<b_class> myb;
public:
  a_class()
  : myb(new b_class(this)) {}
  template<typename B>
  void change_myb()
  {
    myb.reset(new B(this));
  }
};

1

为什么不使用http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/enable_shared_from_this.html呢?

struct a_class : enable_shared_from_this<a_class> {
    a_class() {
        shared_ptr<a_class> ptr(this);
        register_somewhere(ptr);
    }
};

更新:这里是一个完整的工作示例:

#include <stdio.h>
#include <boost/smart_ptr/enable_shared_from_this.hpp>

struct a_class;
boost::shared_ptr<a_class> pa;

void register_somewhere(boost::shared_ptr<a_class> p)
{
    pa = p;
};

struct a_class : boost::enable_shared_from_this<a_class> {
private:
    a_class() {
        printf("%s\n", __PRETTY_FUNCTION__);
        boost::shared_ptr<a_class> ptr(this);
        register_somewhere(ptr);
    }

public:
    ~a_class() {
        printf("%s\n", __PRETTY_FUNCTION__);
    }

    static boost::shared_ptr<a_class> create()
    {
        return (new a_class)->shared_from_this();
    }
};

int main()
{
    boost::shared_ptr<a_class> p(a_class::create());
}

请注意工厂函数a_class::create()。它的工作是确保只创建一个引用计数器。因为这个原因,它使用了单例模式。
boost::shared_ptr<a_class> p(new a_class);

会创建两个引用计数器并导致对象被重复删除。


2
我认为这个例子不起作用:你可能会为同一个指针拥有2个不同的计数器... - Emiliano
正确的头文件包含应该是<boost/enable_shared_from_this.hpp>而不是<boost/smart_ptr/enable_shared_from_this.hpp> - Sam Miller
我不喜欢在这里使用全局/静态的外部pa辅助字段,因为它不是线程安全的。例如,不清楚如何在没有这样的hack的情况下使此方法起作用。我认为init方法更加干净/不容易出错。 - Alastair Maw
2
据我所知,那只是“register_somewhere”函数的存根实现,而这与其他方面无关。这在OP中已经明确指出。 - sehe
该方法仅在保证register_somewhere()足够长地持有共享引用时才有效。如果在您调用shared_from_this()之前,它释放了指针,您最终会得到一个已破坏的对象。如果register_somewhere()是一个快速运行的进程,并回调和释放,或者由于某种原因注释了其实现,则可能会发生这种情况。 - SnakE
这个例子是人为的。你可能会想要在create中注册... - Werner Erasmus

0

这是我的解决方案:

class MyClass: enable_shared_from_this<MyClass>
{
    public:
        //If you use this, you will die.
        MyClass(bool areYouBeingAnIdiot = true)
        {
            if (areYouBeingAnIdiot)
            {
                throw exception("Don't call this constructor! Use the Create function!");
            }

            //Can't/Don't use this or shared_from_this() here.
        }

        static shared_ptr<MyClass> Create()
        {
            shared_ptr<MyClass> myClass = make_shared<MyClass>(false);

            //Use myClass or myClass.get() here, now that it is created.

            return myClass;
        }
}

//Somewhere.
shared_ptr<MyClass> myClass = MyClass::Create();

构造函数必须是公共的,才能从静态成员函数调用,即使是内部函数也是如此...


0
为此,我编写了自己的shared_ptrweak_ptrenable_shared_from_this的替代品。你可以在Sourceforge上查看它。
与标准的enable_shared_from_this相比,它允许在构造函数和析构函数中调用shared_from_this(),而无需使用帮助函数,并且没有额外的空间开销。
请注意:只有在创建的shared_ptr在dtor返回之前被销毁时,才允许在dtors中创建shared_ptr。

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