智能指针与可选所有权

6
我尝试让一个类包含一个指针,这个指针可以是拥有的也可以是借用的。如果是前者,它应该自己销毁所拥有的对象;如果是后者,它不应该销毁被指向的对象。
在代码中,我有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的实例被销毁时,它会销毁c1、b1和b2。理想情况下,销毁b2应该删除匿名的C实例,但是销毁b1不应该删除任何东西(因为c1将直接被A销毁)。您可以使用什么样的智能指针来实现这一点?或者,最好的解决方案是将所有权标志传递给B吗?

1
这听起来像是一场噩梦。你如何考虑这种“可选所有权”?指针是唯一属于你的,还是与他人共享?线程安全?可重入?还是全部归你? - Kerrek SB
1
std::shared_ptr即使允许这些诡计,但如果默认删除器不同(如null删除器),则必须手动设置正确的删除器。或者,使用指向拥有对象的shared_ptr以获得完全正确的语义。 - Deduplicator
@Deduplicator删除器是一个模板参数,因此您必须编写一个处理两种情况的删除器。您不能在一种情况下传递空删除器,在另一种情况下传递常规删除器。 - Adam
@AdamпјҡеҰӮдҪ•жһ„йҖ shared_ptrзҡ„еҶіе®ҡдёҺдј йҖ’жүҖжңүжқғж Үеҝ—зҡ„ж–№ејҸзӣёеҗҢпјҢз”ҡиҮіеҸҜд»ҘжҢүз…§жҲ‘第дәҢз§Қжһ„йҖ ж–№ејҸжӣҙеҘҪең°иҝӣиЎҢгҖӮ - Deduplicator
@KerrekSB:可以使用std::shared_ptr来合理地完成。这并不意味着在OP的情况下这样做是有道理的,但是可能会这样做。 - Deduplicator
显示剩余8条评论
4个回答

2

如果您确信并能够保证重用的C不会早期被销毁(请三次检查),则有多种方法可以实现。
以下是一些您可能会考虑的方法:

  1. You can manually manage the pointer and a flag. Make sure you get the copy-semantic right, e.g. like this:

    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. You could use a custom deleter, like this:

    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. You could take a peek at std::shared_ptr, though that is probably severe overkill and might have too much overhead for you.

使用自定义删除器的解决方案似乎更加简洁。 - Jimmy T.

1

通过使用 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

1

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

0

据我所知,没有办法在不产生副作用的情况下实现这种行为。如果只是普通指针(不是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*构造时,它不会增加引用计数,而是创建一个新的shared_ptr,引用计数为1。

这不是你问题的答案,而是一些常见用法。在答案中查看Deduplicator中的unique_ptr。还要检查:http://www.boost.org/doc/libs/1_55_0/libs/smart_ptr/smart_ptr.htm。即使你不使用boost,也有很好的理论来使用不同的方法来持有对象。还要检查这个答案:什么是智能指针,何时应该使用它们?


我不认为这需要一个共享指针的额外开销(和混乱),因为它不是共享的。该对象要么被拥有,要么未被拥有,在编译时我们知道它是哪个,我们只需要一个良好的 API 来处理这两种情况。 - c z

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