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
的目的是为动态分配的内存提供自动和异常安全的重新分配(不像原始指针必须显式 delete
d 才能被释放,这很容易在交错异常的情况下无意中没有被释放)。
不过,您的问题更多是关于指针的一般值,而不是具体的 std::unique_ptr
。对于像 int
这样的简单内置类型,通常没有什么理由使用指针而不是简单地按值传递或存储对象。但是,在三种情况下指针是必要的或有用的:
- 表示单独的 "not set" 或 "invalid" 值。
- 允许修改。
- 允许不同的多态运行时类型。
无效或未设置
一个指针支持一个额外的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 是一个用例。除此之外,还有另外两个:
对象的可选实例。我的 class 可能会延迟实例化对象的实例。为此,我需要使用内存分配,但仍然想要 RAII 的好处。
与 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
解决方案更易于维护。
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
的目的是为动态分配的内存提供自动和异常安全的重新分配(不像原始指针必须显式 delete
d 才能被释放,这很容易在交错异常的情况下无意中没有被释放)。
不过,您的问题更多是关于指针的一般值,而不是具体的 std::unique_ptr
。对于像 int
这样的简单内置类型,通常没有什么理由使用指针而不是简单地按值传递或存储对象。但是,在三种情况下指针是必要的或有用的:
- 表示单独的 "not set" 或 "invalid" 值。
- 允许修改。
- 允许不同的多态运行时类型。
无效或未设置
一个指针支持一个额外的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 是一个用例。除此之外,还有另外两个:
对象的可选实例。我的 class 可能会延迟实例化对象的实例。为此,我需要使用内存分配,但仍然想要 RAII 的好处。
与 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
解决方案更易于维护。