C++0x中的unique_ptr是否取代了scoped_ptr的所有权?

20

我以前写过这样的代码:

class P {};

class Q: public P {};

class A {
    // takes ownership
    A(P* p): p_(p) {}

    scoped_ptr<P> p_;
};

A a(new Q);

C++0x之后,我需要将类A重写为:

class A {
    // takes ownership
    A(unique_ptr<P>&& p): p_(p) {}

    unique_ptr<P> p_;
};

1
同样地,是否有C++0x替代boost::scoped_array的方法? - rafak
2
@rafak std::unique_ptr 也适用于数组(它将调用delete[])。 - Cubbi
16
使用unique_ptr的方法如下:std::unique_ptr<P[]>。unique_ptr不仅在销毁时调用delete[],而且它禁用了*和->运算符,而提供了一个[]运算符。 - deft_code
5个回答

44

我已经点赞了comonad的回答,但有一个注意点:

每当您想明确禁用移动语义时,请使用scoped_ptr const unique_ptr

我还没有遇到过任何情况下const std::unique_ptrboost::scoped_ptr差。然而,我很乐意接受相关方面的教育。

编辑:

这里有一个boost::scoped_ptr的用例,我认为它应该失败,但实际上不会。但对于std::unique_ptr它是失败的:

#include <iostream>

#ifdef USE_UNIQUEPTR

#include <memory>
typedef std::unique_ptr<int> P;

#else  // USE_UNIQUEPTR

#include <boost/scoped_ptr.hpp>
typedef boost::scoped_ptr<int> P;

#endif  // USE_UNIQUEPTR

int main()
{
    P p1(new int(1));
    {
        // new scope
#ifdef USE_UNIQUEPTR
        const P p2(new int(2));
#else  // USE_UNIQUEPTR
        P p2(new int(2));
#endif  // USE_UNIQUEPTR
        swap(p1, p2);  // should fail!
    }
    std::cout << *p1 << '\n';
}
如果 `boost::scoped_ptr` 的承诺是其资源不会逃出当前作用域,那么相比于 `const std::unique_ptr`,它并不能像后者一样好地履行这一承诺。如果我们想将 `const boost::scoped_ptr` 与 `const std::unique_ptr` 进行比较,我必须问:为了什么目的?它们对我来说似乎没什么区别,除了 `const std::unique_ptr` 允许自定义构造和析构。

1
不错的技巧!我没有想到使用任何const指针。实际上,在某些情况下,我想禁止复制和移动语义,但仍然想使用交换语义。只是我不想有任何空指针 - 即使在竞态条件下也不行。如果不允许交换语义,const scoped_ptr和const unique_ptr之间会有什么区别吗? - comonad
1
我能想到的const scoped_ptr和const unique_ptr之间唯一的区别是后者允许自定义解分配器和指针类型。如果你想使用swap,但不想移动或复制,那么scoped_ptr听起来是最合适的选择。然而,scoped_ptr像unique_ptr一样容易允许nulls。你可能需要一个尚未讨论过(我也没有见过)的智能指针:not_null_ptr。 - Howard Hinnant
一个 scoped_ptr 可以被重置为不同的指针。那么 const unique_ptr 允许这样做吗? - balki
不行。一个 const unique_ptr 表示:我从构造到析构一直持有这个指针。 - Howard Hinnant

30
  • auto_ptr是一个具有复制和移动语义以及所有权(=自动删除)的指针。
  • unique_ptr是一个没有复制但具有移动语义的auto_ptr
  • scoped_ptr是一个没有复制和移动语义的auto_ptr

    auto_ptr总是一个不良选择 - 这是显而易见的。

    每当您想要显式地具有移动语义时,请使用unique_ptr

    每当您想要显式禁止移动语义时,请使用scoped_ptr

  • 所有指针都允许交换语义,例如p.swap(q)。要禁止这些,请使用任何const …_ptr

在某些情况下,您希望使用scoped_ptr指向多个可互换对象之一:由于不存在移动语义,因此它非常安全(从明显的错误方面考虑)不会意外指向null。值得一提的是:scoped_ptr仍然可以有效地进行交换。为了使其可移动和/或可复制 - 但仍具有这些交换语义 - 您可能需要考虑使用指向可交换对象(通过scoped_ptr::swap)的scoped_ptrshared_ptr

更多细节请参见stackoverflow:smart-pointers-boost-explained


我同意你的观点,看起来你也同意詹姆斯的答案。感谢你抽出时间回答。 - Neil G

2
使用unique_ptr更好,因为它提供了一个额外的功能:移动语义。也就是说,您可以为您的类编写移动构造函数等,与scoped_ptr不同。此外,unique_ptr没有与之相关的开销,因此它是一种优越的工具。当然,在您不需要移动语义的情况下,是否重写代码取决于您自己的决定。不要忘记unique_ptr来自标准库,因此它必须与任何符合C++0x(当然,如果它变成现实)的实现一起提供!

5
scoped_ptr 不会产生开销。你可能在想 shared_ptr :) - Billy ONeal
@Billy ONeal 有时候我不知道该如何用英语表达某些东西 :) 基本上,我的意思是,两者都不会产生额外的开销。 - Khaled Alshaya
如果它们都不会产生额外开销,那么为什么一个相对于另一个是“更优”的呢?(引用 -> “与scoped_ptr不同,unique_ptr没有与之相关的开销,因此它是一种更优越的工具”) - Billy ONeal

2

我不同意AraK的观点,认为没有一种指针是优越的选择,这通常取决于使用情况。这就好比说智能小车在所有用途上都比皮卡车更优越,因为它更轻更快。实际上,有时你需要一辆卡车,有时你不需要。你的指针选择应该基于你的需求。

scoped_ptr的好处在于它增加了一层安全性。通过使用scoped_ptr,您声明创建的内存仅存在于该作用域中,因此您会在编译时获得保护,防止尝试移动或传输它。

因此,如果您想创建某个东西但又限制其范围,请使用scoped_ptr。如果您想创建某个东西并使所有权可移动,请使用unique_ptr。如果您想创建某个东西并共享该指针,并在所有引用者消失时进行清理,请使用shared_ptr。


抱歉,但这是完全错误的。最大的问题是 unique_ptr 是标准中的内容,而 scoped_ptr 不是。因此,如果 C++0x 编译器适合您的应用程序,您应该始终使用 unique_ptr - Billy ONeal
3
@Billy,根据你的经验,对于那些花时间回答问题的新社区成员,多表现出善意可能更好一些 ;) - Neil G

1

编辑:我的错,您确实需要在初始化器中编写move(p)std::move将其给定的任何内容视为右值引用,在您的情况下,即使您的参数是对某个东西的右值引用,将其传递给其他东西(例如p_的构造函数)也将默认传递一个左值引用,而不是右值引用。

根据Karu的评论,还添加了必要的包含文件以使我的代码可编译。

例如:

#include <memory>
#include <cassert>
#include <vector>
using namespace std;

class A {};

class B {
public:
  void takeOwnershipOf(unique_ptr<A>&& rhs) {
    // We need to explicitly cast rhs to an rvalue when passing it to push_back
    // (otherwise it would be passed as an lvalue by default, no matter what
    // qualifier it has in the argument list).  When we do that, the move
    // constructor of unique_ptr will take ownership of the pointed-to value
    // inside rhs, thus making rhs point to nothing.
    owned_objects.push_back(std::move(rhs));
  }

private:
  vector<unique_ptr<A>> owned_objects;
};

int main() {
  unique_ptr<B> b(new B());
  // we don't need to use std::move here, because the argument is an rvalue,
  // so it will automatically be transformed into an rvalue reference.
  b->takeOwnershipOf( unique_ptr<A>(new A()) );

  unique_ptr<A> a (new A());
  // a points to something
  assert(a);
  // however, here a is an lvalue (it can be assigned to). Thus we must use
  // std::move to convert a into an rvalue reference.
  b->takeOwnershipOf( std::move(a) );
  // whatever a pointed to has now been moved; a doesn't own it anymore, so
  // a points to 0.
  assert(!a);
  return 0;
}

另外,在您的原始示例中,您应该像这样重写 A 类:

class A { // takes ownership A(unique_ptr

&& p): p_(std::move(p)) {}

unique_ptr<P> p_;

};


@Dan:在你的例子中,owned_objects.push_back(rhs); 不会调用 push_back 的 rvalue 版本。你仍需要说 owned_objects.push_back(std::move(rhs)); 才能实现这一点。rhs 是一个 rvalue 引用(即变量 rhs 的类型),但是表达式 rhs 产生的结果却是一个 lvalue(例如,你可以说 rhs = something; 或者 &rhs; 等)。这可能会让人感到困惑,但是当 rvalue 引用变量被提及为一个表达式时,其结果是一个 lvalue。 - Karu

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