懒惰构造的shared_ptr

时间:2015-09-17 21:29:42

标签: c++ c++11 std wrapper shared-ptr

编辑:完全重新编辑,因为原始版本变成了非结构化的混乱:)感谢所有人的输入到目前为止;我希望我在下面的文本中使用它。

问题

我正在寻找一个懒惰创建的可共享指针。我有一个假设的大班Thing。事情是巨大的,因此制造成本很高,但是虽然它们在代码中的任何地方使用(共享,经过大量传递,修改,存储以供以后使用等),但它们实际上通常不会被最终使用,因此会延迟实际创建直到实际访问它们是可取的。因此,需要懒惰地创造,并且需要可分享。让我们调用这个封装指针包装器SharedThing。

class SharedThing {
  ...
  Thing* m_pThing;
  Thing* operator ->() {
    // ensure m_pThing is created
    ...
    // then
    return m_pThing
  );
}
...
SharedThing pThing;
...
// Myriads of obscure paths taking the pThing to all dark corners
// of the program, some paths not even touching it
...
if (condition) {
  pThing->doIt();   // last usage here
}

要求

  1. 实际事物的实例化必须尽可能延迟;只有在首次取消引用SharedThing
  2. 时才会创建
  3. SharedThing必须安全使用,因此不需要工厂方法
  4. SharedThing必须具有shared_ptr(like)接口
  5. 与尚未创建的SharedThing共享必须实际共享要创建的Thing,但Thing的实例化必须再次延迟直到需要
  6. 使用SharedThings必须尽可能简单(最好是100%透明,就像使用实际的东西一样)
  7. 它必须有点高效
  8. 到目前为止,我们已经提出了四种选择:

    选项1

    typedef std::shared_ptr<Thing> SharedThing;
    SharedThing newThing() {
      return make_shared<Thing>();
    }
    ...
    // SharedThing pThing; // pThing points to nullptr, though...
    SharedThing pThing(new Thing()); // much better
    SharedThing pThing = newThing(); // alternative
    
    1. 0%得分;从一开始就需要一个Thing实例
    2. 0%得分;你可以说SharedThing pThing;但这有点过分担心事情
    3. 这里100%得分;)
    4. N.A。由于第1点
    5. 100%得分
    6. 0%得分,因为到处创造所有的东西(即使没有使用)是对性能的消耗,这正是我问这个问题的原因:)
    7. 第1点和第6点缺乏得分是一个杀手;没有选择1。

      选项2

      class SharedThing: public shared_ptr<Thing> {};
      

      并覆盖特定成员,以确保在取消引用shared_ptr时,它会及时创建Thing。

      1. 也许可以通过覆盖正确的成员来实现(取决于stl的实现),但这很快就会变得混乱我认为
      2. 100%得分
      3. 100%得分,虽然模仿所有构造函数和运算符是相当一些工作
      4. 不知道这是否可以......
      5. 100%得分
      6. 100%得分,如果内部事情巧妙地完成
      7. 此选项优于1并且可能没问题,但看起来很混乱和/或黑客......

        选项3.1

        class SharedThing {
          std::shared_ptr<Thing> m_pThing;
          void EnsureThingPresent() {
            if (m_pThing == nullptr) m_pThing = std::make_shared<Thing>();
          }
        public:
          SharedThing(): m_pThing(nullptr) {};
          Thing* operator ->() {
            EnsureThingCreated();
            return m_pThing.get();
          }
        }    
        

        为operator *和const版本添加额外的包装方法。

        1. 100%得分
        2. 100%得分
        3. 可以,但必须单独创建所有界面成员
        4. 0%得分;当附加到nullptr的SharedThing(例如operator =)时,它需要首先创建Thing以便能够共享
        5. 再次100%得分
        6. 50%得分; 2个间接
        7. 这个在4上惨遭失败,所以这个也就失败了。

          选项3.2

          class SharedThing {
            typedef unique_ptr<Thing> UniqueThing;
            shared_ptr<UniqueThing> m_pThing;
          }
          

          并添加3.1中的所有其他方法

          1. 100%得分
          2. 100%得分
          3. 可以,但必须单独创建所有界面成员
          4. 100%得分
          5. 再次100%得分
          6. 25%得分?我们在这里有3个间接......
          7. 除了建议的性能之外,这似乎没问题(但需要测试)。

            选项4

            class LazyCreatedThing {
              Thing* m_pThing;
            }
            typedef shared_ptr<LazyCreatedThing> SharedThing;
            SharedThing makeThing() {
              return make_shared<LazyCreatedThing>();
            }
            

            并添加各种运算符 - &gt;重载使LazyCreatedThing看起来像Thing *

            1. 100%得分
            2. 与上面的选项1相同的缺点
            3. 100%在这里毫不费力地得分
            4. 100%得分
            5. 0%得分;取消引用SharedThing会产生一个LazyCreatedThing,所以即使它可能有它的运算符 - &gt;为了访问Thing,它永远不会被链接,导致(* pThing) - &gt; doIt();
            6. 25-50%得分?我们这里有3个间接,如果我们可以使用std :: make_shared
            7. ,则有2个

              在这里惨败5使这成为禁忌。

              结论

              迄今为止最好的选择似乎是3.2;让我们看看我们还能想出什么! :)

2 个答案:

答案 0 :(得分:3)

我会实现LazyThing包装器。我想调整Thing界面而不是std::shared_ptr界面要容易得多。

class Thing
{
public:
    void Do()
    {
        std::cout << "THING" << std::endl;
    }
};

class LazyThing
{
public:
    void Do()
    {
        getThing().Do();
    }

private:
    Thing& getThing()
    {
        if (!thing_)
            thing_ = std::make_unique<Thing>();

        return *thing_;
    }

    std::unique_ptr<Thing> thing_;
};

现在,您可以将它与任何智能指针一起使用,甚至可以在堆栈上创建它:

LazyThing lazy;
auto sharedLazy = std::make_shared<LazyThing>();
auto uniqueLazy = std::make_unique<LazyThing>();

或者如你的例子所示:

typedef std::shared_ptr<LazyThing> SharedLazyThing;

SharedLazyThing newThing() {
  return std::make_shared<LazyThing>();
}
...
auto pThing = newThing();

<强>更新

如果您想保证共享语义并且不打扰调用newThing()或任何其他工厂方法,请放弃shared_ptr接口。那里不需要它。

SharedLazyThing实现为具有共享语义的值类型。棘手的是你需要添加另一个间接层来提供共享Thing对象的延迟构造。

class SharedLazyThing
{
   using UniqueThing = std::unique_ptr<Thing>;

public:
   void Do()
   {
      getThing().Do();
   }

private:
   Thing& getThing()
   {
      if (!*thing_)
         *thing_ = std::make_unique<Thing>();

      return **thing_;
   }

   std::shared_ptr<UniqueThing> thing_ = std::make_shared<UniqueThing>();
};

现在,您可以随处使用SharedLazyThing

SharedLazyThing thing1;
SharedLazyThing thing2(thing1);
SharedLazyThing thing3 = thing1;

答案 1 :(得分:0)

也许我误解了这个问题但不能像这样简单吗?

class Factory
{
private:

  std::shared_ptr<My1stType> my1st_ {};
  std::shared_ptr<My2ndType> my2nd_ {};
  std::shared_ptr<My3rdType> my3rd_ {};
  // …

public:

  std::shared_ptr<My1stType>
  get1st()
  {
    if (!this->my1st_)
      this->my1st_ = std::make_shared<My1stType>(/* … */);
    return this->my1st_;
  }

  // …
};

如上所示,这不是线程安全的,但是,如果这对您很重要。