对于所有权,使用unique_ptr,否则使用原始指针?

8

我正在使用C++11编写一些代码。我有

class X { /* */ };

class A {
    std::vector<X*> va_x;
};

class B {
    std::vector<X*> vb_x;
    std::vector<A> vb_a;
};

我的A类中的“va_x”的X*指向的对象也被B类中的“vb_x”的X*所指向。

现在我想使用智能指针。对我来说,很明显B类拥有被X*指向的对象的所有权(特别是因为我的A实例属于B)

因此,我应该在B内使用unique_ptr来管理X:

class B {
    std::vector<unique_ptr<X>> vb_x;
    std::vector<A> vb_a;
};

我的问题是,对于A类我应该怎么做?我是否应该保留裸指针?这样做,在我的单元测试中,我必须承认会导致一些尴尬的事情(在我看来),例如(不要担心封装,那不是重点):

unique_ptr<X> x(new X());
A a;
a.va_x.push_back(&(*x)); //awkward, but what else can I do?

A.vb_a.push_back(a); //ok
B.vb_x.push_back(move(x)); //ok

1
你之前不是问过一个几乎完全相同的问题吗? - Puppy
你最好一开始就不要在向量中存储原始指针!为什么不将独立指针放入向量中呢? - Kerrek SB
@Kerrek:请再次阅读问题,原帖确实如此。 - Xeo
@KerrekSB 因为这些 unique_ptr 已经在向量“B::vb_x”中了。我不能同时将它们放在“A::va_x”中,因为它们是唯一的。除非我对整个唯一的事情有什么误解... - Bérenger
1
在这种情况下,将引用包装器存储在 A 中,并构建您的代码,使得 B 对象必须首先存在。 - Kerrek SB
显示剩余3条评论
2个回答

10

你可以使用x.get(),它将返回内部指针。

除此之外,是的,使用原始指针来处理非拥有引用是可行的方式,也可以参考这个问题


好的,谢谢。但这并没有好多少对吧?所以我还是应该使用原始指针? - Bérenger
@BérengerBerthoul:只要它们不拥有任何东西,使用原始指针就没有任何问题。正如名称所示,它们应该只是指向事物,一切都很好。 - Xeo
猜想问题已经解决了。我对这些新功能非常谨慎,因为我没有看到任何真正使用它们的代码。而且,由于我听到“用智能指针替换原始指针”的声音越来越多,我开始认为原始指针不再需要了(这并不是很明智,我承认...)。谢谢。 - Bérenger
4
@Xeo: 我不同意"原始指针没有问题"。因为原始指针缺乏所有权语义,所以它们是一个概念上的噩梦。要么使用独占指针来表示所有权,要么使用引用(包装器)来表示环境拥有的对象。 - Kerrek SB

-1
如Xeo在他的回答中所说,解决方案通常是使用x.get()
这里有一个使用x.get()FILE示例,每当需要访问它时都会使用它:
void file_deleter(FILE *f)
{
    fclose(f);
}

[...]

{
    std::unique_ptr<FILE, decltype(&file_deleter)>
                       f(fopen("/tmp/test.tmp", "rw"), &file_deleter);

    // read/write use f.get() as in:
    fread(buf, 1, sizeof(buf), f.get());
    fwrite(buf, 1, sizeof(buf), f.get());

    // fclose() gets called in the deleter, no need to do anything
}

然而,在您的情况下,您需要使用x.release()

A a;
{
    unique_ptr<X> x(new X());
    a.va_x.push_back(&(*x)); // this is wrong
}
// here a[0] is a dangling pointer

&(*x)将获取原始指针并在a向量中进行复制。然而,在unique_ptr<>超出范围的时候,该指针被删除。因此,在}之后,a向量中的指针不再有效(尽管它可能会工作一段时间)。

传输裸指针的正确方法是使用release()函数,如下所示:

    a.va_x.push_back(x.release()); // this works

在那一行之后,指针只存在于 a 向量中。它从唯一指针中释放出来,意味着调用者现在负责管理该资源。

重要提示:push_back() 可能会抛出 (bad_alloc) 异常,如果发生这种情况,资源将会丢失。为了避免这个问题(假设您的软件捕获了 bad_alloc 并继续运行),您需要首先在向量中预留空间,如下所示:

    a.va_x.reserve(a.va_x.size() + 1);  // malloc() happens here
    a.va_x.push_back(x.release());      // no `bad_alloc` possible here

这样,当资源仍然附加在unique_ptr上并且发生异常时,bad_alloc会在该语句上发生,从而避免了泄漏。

所有这些都说了,你可能需要使用shared_ptr。它们可以轻松复制。 unique_ptr更适用于分配一次资源,然后在函数返回或对象被删除时忘记。当涉及(许多)副本时,shared_ptr更有意义。

class X
{
    typedef std::shared_ptr<X> pointer_t;
    [...]
}

class A
{
    std::vector<X::pointer_t> va_x;
}

X::pointer_t x(new X());
A a;
a.va_x.push_back(x); // much cleaner and the pointer is still managed

然而,在你的情况下,你需要使用x.release()。不过这样做完全违背了使用独特指针的初衷,因为现在我必须负责删除指针。 - Bérenger
@Bérenger 如果你不这样做,那么你必须确保在 unique_ptr 实例之前向量超出作用域。如果你可以在你的特定代码中做到这一点,那么你就很好了。 - Alexis Wilke

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