c++ 具有可选所有权的智能指针

6psbrbz9  于 2022-11-20  发布在  其他
关注(0)|答案(4)|浏览(144)

我试图允许一个类包含一个指针,这个指针可以是一个被拥有的指针,也可以是一个被借用的指针。在后一种情况下,它不应该破坏指向的对象。
在代码中,我有类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吗?

mqxuamgl

mqxuamgl1#

如果您确信并且能够保证重用的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(); }
};

1.您可以使用自定义删除器,如下所示:

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

1.您可以看一下std::shared_ptr,尽管这可能是严重的矫枉过正,而且可能会有太多的开销。

xuo3flqw

xuo3flqw2#

虽然我担心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()) {}
};
qv7cva1a

qv7cva1a3#

据我所知,没有办法在没有副作用的情况下存档这种行为。如果它只是普通的指针(不是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*) {}
};

或者使用&(cleaner,如果你的框架接受它的话):

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?

nszi6y05

nszi6y054#

通过std::move为 * 拥有的 * 版本传入unique_ptr,并为无拥有的版本传入引用

  • unique_ptr之外没有运行时开销
  • 避免错误使用
  • 避免了使用自定义析构函数
  • 无歧义

最小工作示例:

#include <iostream>
#include <memory>

class C
{
public:
    ~C() { std::cout << "Goodbye\n"; }

    void SayHello() { std::cout << "Hello\n"; }
};

class B
{
    std::unique_ptr<C> owned;
    C* unowned;

public:
    B(C& c) : owned(nullptr)
            , unowned(&c)
    { }

    B(std::unique_ptr<C> c) : owned(std::move(c))
                            , unowned(owned.get())
    { }

    C& GetC() { return *unowned; }
};

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

相关问题