【问题标题】:shared_from_this called from constructor从构造函数调用 shared_from_this
【发布时间】:2011-03-24 23:34: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;
}

在这种情况下可以遵循标准程序吗?

编辑:其实我的情况更复杂。我有 2 个对象,它们应相互维护指针。所以事实是我不是在“注册”,而是创建另一个需要this 作为参数的对象(比如说b_class)。 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 实例。

您是否建议在这种情况下避免使用智能指针?

【问题讨论】:

  • 我看不到你的代码,但显而易见的问题是为什么register_somewhere 需要智能指针?如果一个对象不管理自己的生命周期,它通常会更干净(这是假设或要求它将由智能指针持有是楔形的细端)。无论是创建对象,都应该根据分配策略来决定,静态的、自动的、由智能指针管理的动态等。如果可以将注册和所有权分开,那就更好了。
  • 简单解决方案:sourceforge.net/projects/shared-from-this-ctor/

标签: c++ boost


【解决方案1】:

a_class 负责创建和销毁b_class 实例

...

a_class 实例“存活”b_class 实例。

鉴于这两个事实,b_class 实例在a_class 实例被销毁后尝试访问a_class 实例应该没有危险,因为a_class 实例负责销毁@987654329 @ 实例。

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

【讨论】:

    【解决方案2】:

    如果你在构建过程中绝对需要一个 shared_ptr,最好有一个“init”函数。事实上,这是我能想到的唯一体面的方法。如果您选择此路径,您可能应该有一个创建此类对象的特殊函数,以确保调用 init()

    但是,根据您要注册的内容,最好在构造函数中使用指向该对象的普通指针而不是 shared_ptr 来提供您要注册的任何对象。然后在析构函数中,你可以从管理器中注销对象。

    【讨论】:

    • 如果管理器是异步注销并不能保证在您的对象被销毁后的一毫秒内不会调用您的回调。共享指针和弱指针的组合可以解决这样的竞争。
    【解决方案3】:

    你为什么不用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 个不同的计数器...
    • 要包含的正确标题是 &lt;boost/enable_shared_from_this.hpp&gt; 而不是 &lt;boost/smart_ptr/enable_shared_from_this.hpp&gt;
    • 不喜欢这里的全局/静态外部pa 辅助字段,因为它不是线程安全的,例如。目前尚不清楚如何在没有这种 hack 的情况下使这种方法发挥作用。我认为 init 方法更简洁/不易出错。
    • @AlastairMaw AFAICT 只是一个存根实现 - 否则不相关 - register_somewhere 函数。这是 OP 中给出的。
    • 只有当register_somewhere() 保证保持共享引用足够长时,此方法才有效。如果它在你有机会调用shared_from_this() 之前释放指针,你最终会得到一个被破坏的对象。如果 register_somewhere() 是例如一个快速运行的进程,它会回调并释放,或者它的实现是否因任何原因被注释掉。
    【解决方案4】:

    不需要在您的代码中使用 shared_ptr(当您展示并解释它时)。 shared_ptr适用于共享所有权。

    您的b_class 并不拥有它的a_class,实际上它甚至被它活得更久,所以它应该只保留一个观察 指针。

    如果b_class 是多态的并且a_class 的操作涉及更改其b_class 指针,则应使用unique_ptr&lt;b_class&gt;

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

    【讨论】:

      【解决方案5】:

      我想出了一个帮助类来解决这个问题:

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

      有点难看,但可以。

      【讨论】:

        【解决方案6】:

        为此,我为shared_ptrweak_ptrenable_shared_from_this 编写了自己的替代品。您可以通过Sourceforge查看。

        与标准 enable_shared_from_this 相比,它允许在构造函数和析构函数中调用 shared_from_this() 没有辅助函数,而无需空间开销。

        注意:只允许在 dtors 中创建 shared_ptr,前提是它们只是临时创建的。也就是说,它们在 dtor 返回之前就被销毁了。

        【讨论】:

          【解决方案7】:

          这是我的解决方案:

          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();
          

          (构造函数必须是公共的,才能从静态成员函数调用,甚至是内部函数......)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2023-03-05
            • 1970-01-01
            • 2011-03-24
            • 2017-02-08
            • 1970-01-01
            • 1970-01-01
            • 2010-10-05
            相关资源
            最近更新 更多