【问题标题】:Smart pointers with optional ownership具有可选所有权的智能指针
【发布时间】:2014-07-25 19:41:57
【问题描述】:

我试图让一个类包含一个指针,它可能是一个拥有的指针或一个借用的指针。在前一种情况下,它应该销毁拥有的对象本身;在后一种情况下,它不应该破坏指向的对象。

在代码中,我有 A、B 和 C 类。我的目标是以下(简化的)定义,其中 B 是需要拥有指针的类:

class C {
    ...
};

class B {
    C *c;
    B(C *c) : c(c) {
    }
};

class A {
    C c1;
    B b1, b2;
    // b2 leaks pointer to C
    A() : b1(&c1), b2(new C()) {
    }
};

A 的实例解构时,它会销毁c1b1b2。理想情况下,b2 的销毁应该删除匿名的C 实例,但b1 的销毁不应该删除任何东西(因为c1 会被A 直接销毁)。

我可以使用哪种智能指针来实现这一点?或者,最好的解决方案是将所有权标志传递给 B?

【问题讨论】:

  • 这听起来像是一场噩梦。您如何解释这种“可选所有权”?指针是您独有的,还是您与他人共享?线程安全吗?重入?还是你的全部?
  • std::shared_ptr 甚至允许这些恶作剧。但是,如果它与默认删除器不同,您必须手动设置正确的删除器,例如空删除器。或者,使用指向拥有对象的 shared_ptr 以获得完全正确的语义。
  • @Deduplicator 删除器是一个模板参数,因此您必须编写一个处理这两种情况的删除器。您不能只在一种情况下传递空删除器,而在另一种情况下传递常规删除器。
  • 回答这个问题,“将所有权标志传递给 B 是最好的解决方案吗?”。是的。
  • @Adam:决定如何构造shared_ptr 与传递所有权标志相同。按照我的第二种方式构建它会更好。

标签: c++ smart-pointers


【解决方案1】:

如果您确定并且可以保证重复使用的 C 不会被提前销毁(三重检查),有多种方法可以解决。
一些你可能会考虑的:

  1. 您可以手动管理指针和标志。确保您获得正确的复制语义,例如像这样:

    class B {
        std::unique_ptr<C> c;
        bool shared = false;
    
        B(C& c) : c(&c), shared(true) {}
        B(C *c = 0) : c(c) {}
        ~B() { if (shared) c.release(); }
    };
    
  2. 您可以使用自定义删除器,如下所示:

    template <class T> struct maybe_delete
    {
        void operator()(T* p) const noexcept {if(!shared) delete p;}
        bool shared = false;
    };
    template <class T> struct maybe_delete<T[]>
    {
        void operator()(T* p) const noexcept {if(!shared) delete [] p;}
        template <class U> void operator()(U*) const = delete;
        bool shared = false;
    };
    
    class B {
        std::unique_ptr<C, maybe_delete> c;
    
        B(C& c) : B(&c) {this->c.get_deleter().shared = true;}
        B(C *c) : c(c) {}
    };
    
  3. 您可以查看std::shared_ptr,尽管这可能是严重的矫枉过正并且可能对您来说开销太大。

【讨论】:

  • 带有自定义删除器的解决方案看起来更干净
【解决方案2】:

虽然我担心B 可能遭到滥用,但您可以这样做:

class B {
    C *c;
    bool owned;

    B(C& c) : c(&c), owned(false) {}
    B(C *c) : c(c), owned(true) {}
    ~B() { if (owned) delete c; }
};

class A {
    C c1;
    B b1, b2;
    A() : b1(c1), b2(new C()) {}
};

【讨论】:

    【解决方案3】:

    据我所知,没有任何方法可以在没有副作用的情况下存档此行为。如果它只是普通的指针(不是 COM),那么您可以在两个类中通过 shared_ptr 访问 C。如果只有 B 拥有 C,那么它们都将随着 B 的销毁而被销毁。如果 A 和 B 都拥有 C,则只有在最后一个活着的所有者(无论是 A 还是 B)被销毁时,C 才会被销毁。

    我知道考虑所有权的这种做法: 如果方法只获得一个普通指针,则意味着该指针将仅在该方法内部使用。所以,B 将是:

    class B1 {
        B(C *c) {
          //do some staff with c
        }
        void doSomeStaff(C*) {}
    };
    

    或者使用 & (更简洁,如果你的框架接受的话):

    class B2 {
        B(C& c) {
          //do some staff with c
        }
        void doSomeStaff(C&) {}
    };
    

    如果方法得到一个共享指针,它需要这个指针以备将来重用(保留它):

    class B3 {
    public:
        std::shared_ptr<C> c;
        B(std::shared_ptr<C> c) : c(c) {
        }
    };
    

    所以,现在您可以调用 b1.doSomeStaff(b3.c) 或 b2.doSomeStaff(*b3.c),而无需考虑谁必须销毁指向的对象 C。您只知道,该对象将在 b1 中使用。就是这样。

    不要忘记在方法中指定您需要 shared_ptr,而不是 C* - shared_ptr 是一个对象,它在复制时增加对对象的引用计数。当从 C* 构造时,不是递增,而是创建一个引用计数 = 1 的新 shared_ptr。

    这不是您问题的答案,而是一些常见用途。请参阅 Deduplicator 的答案中的 unique_ptr。另请查看:http://www.boost.org/doc/libs/1_55_0/libs/smart_ptr/smart_ptr.htm。即使您不使用 boost,也有一个很好的理论可以使用不同的方法来保存对象。还要检查这个答案:What is a smart pointer and when should I use one?

    【讨论】:

      【解决方案4】:

      通过std::move拥有版本传入unique_ptr,为未拥有版本传入reference

      • 避免错误使用
      • 避免使用自定义析构函数
      • 没有歧义

      最小的工作示例:

      #include <iostream>
      #include <memory>
      
      class C
      {
      public:
          ~C()
          {
              std::cout << "Goodbye\n";
          }
      
          void SayHello()
          {
              std::cout << "Hello\n";
          }
      };
      
      class B
      {
          C* unowned;
          std::unique_ptr<C> owned;
      
      public:
          B(C& c)
                  : unowned(&c)
                    , owned(nullptr)
          {
      
          }
      
          B(std::unique_ptr<C> c)
                  : unowned(nullptr)
                    , owned(std::move(c))
          {
      
          }
      
          C& GetC()
          {
              return unowned ? *unowned : *owned;
          }
      };
      
      int main()
      {
          C stackC;
          std::unique_ptr<C> heapC(new C);
      
          B b1(stackC);
          B b2(std::move(heapC));
      
          b1.GetC().SayHello();
          b2.GetC().SayHello();
      
      }
      

      输出:

      Hello
      Hello
      Goodbye
      Goodbye
      
      • 如果 B 比 C 寿命长,我会使用它。否则我会使用 shared_ptr,或者更可能的是,重写我的程序。
      • 如果 C 很小且可默认构造,我很想用 C owned 替换 std::unique_ptr&lt;C&gt; owned

      【讨论】:

        猜你喜欢
        • 2020-07-28
        • 2011-05-27
        • 2017-04-29
        • 2020-03-28
        • 2020-06-03
        • 2014-09-25
        • 1970-01-01
        • 2012-07-07
        • 1970-01-01
        相关资源
        最近更新 更多