unique_ptr 有什么意义?

What's the point of unique_ptr?

unique_ptr 本质上不是对象的直接实例吗?我的意思是,动态继承和性能存在一些差异,但 unique_ptr 是全部吗?

考虑此代码以了解我的意思。这不是吗:

#include <iostream>
#include <memory>

using namespace std;

void print(int a) {
    cout << a << "\n";
}

int main()
{
   unique_ptr<int> a(new int);
   print(*a);

   return 0;
}

几乎和这个一模一样:

#include <iostream>
#include <memory>

using namespace std;

void print(int a) {
    cout << a << "\n";
}

int main()
{
   int a;
   print(a);

   return 0;
}

还是我误解了 unique_ptr 的用途?

std::unique_ptr 的目的是为动态分配的内存提供自动和异常安全的重新分配(不像原始指针必须显式 deleted 才能被释放,这很容易在交错异常的情况下无意中没有被释放)。

不过,您的问题更多是关于指针的一般值,而不是具体的 std::unique_ptr。对于像 int 这样的简单内置类型,通常没有什么理由使用指针而不是简单地按值传递或存储对象。但是,在三种情况下指针是必要的或有用的:

  1. 表示单独的 "not set" 或 "invalid" 值。
  2. 允许修改。
  3. 允许不同的多态运行时类型。

无效或未设置

一个指针支持一个额外的nullptr值,表示指针还没有被设置。例如,如果你想支持给定类型的所有值(例如整个整数范围),但也表示用户从不在界面中输入值的概念,那么使用 std::unique_ptr<int>, 因为你可以得到指针是否为 null 作为指示它是否被设置的一种方式(不必丢弃整数的有效值只是为了将该特定值用作无效值,"sentinel" 值表示它没有设置)。

允许修改

这也可以通过引用而不是指针来实现,但指针是实现此目的的一种方式。如果您使用常规值,那么您正在处理原始值的副本,任何修改只会影响该副本。如果您使用指针或引用,您可以让原始实例的所有者看到您的修改。有了唯一指针,你可以额外保证没有其他人有副本,所以不加锁修改是安全的。

多态类型

这同样可以通过引用来完成,而不仅仅是通过指针,但在某些情况下,由于所有权或分配的语义,您可能希望使用指针来执行此操作...当涉及到用户时-定义的类型,可以创建分层 "inheritance" 关系。如果您希望代码对给定类型的所有变体进行操作,则需要使用指针或对基类型的引用。将 std::unique_ptr<> 用于此类事情的一个常见原因是,如果对象是通过工厂构造的,而您定义的 class 维护构造对象的所有权。例如:

class Airline {
 public:
  Airline(const AirplaneFactory& factory);
  // ...
 private:
  // ...
  void AddAirplaneToInventory();
  // Can create many different type of airplanes, such as
  // a Boeing747 or an Airbus320
  const AirplaneFactory& airplane_factory_;
  std::vector<std::unique_ptr<Airplane>> airplanes_;
};

// ...
void Airline::AddAirplaneToInventory() {
  airplanes_.push_back(airplane_factory_.Create());
}

如您所述,虚拟 classes 是一个用例。除此之外,还有另外两个:

  1. 对象的可选实例。我的 class 可能会延迟实例化对象的实例。为此,我需要使用内存分配,但仍然想要 RAII 的好处。

  2. 与 C 库或其他喜欢返回裸指针的库集成。例如,OpenSSL returns 来自许多(记录不完整的)方法的指针,其中一些您需要清理。拥有一个不可复制的指针容器非常适合这种情况,因为我可以在它返回后立即保护它。

A unique_ptr 的功能与普通指针相同,只是您不必记得释放它(实际上它只是指针的包装器)。分配内存后,您不必再调用指针上的 delete,因为 unique_ptr 上的析构函数会为您处理这件事。

除了 Chris Pitman 提到的情况之外,还有一种情况你会想使用 std::unique_ptr 是如果你实例化足够大的对象,那么在堆中而不是在堆。堆栈大小不是无限的,迟早你可能 运行 进入堆栈溢出。这就是 std::unique_ptr 有用的地方。

我想到两件事:

  • 您可以将它用作通用的异常安全 RAII 包装器。任何具有 "close" 函数的资源都可以使用自定义删除器轻松地用 unique_ptr 包装。

  • 有时您可能不得不在不知道其生命周期的情况下四处移动指针。如果您知道的唯一约束是唯一性,那么 unique_ptr 是一个简单的解决方案。在那种情况下,您几乎总是可以进行手动内存管理,但它不会自动异常安全,您可能会忘记 delete。或者您必须 delete 在代码中的位置可能会改变。 unique_ptr 解决方案更易于维护。