目录
unique_ptr设计目标
使用unique_ptr
通过unique_ptr转移所有权
源和槽
unique_ptr作为类成员
处理数组
default_delete<>类
其他关联资源的删除器
unique_ptr详细信息
C++11标准库提供的唯一指针unique_ptr有助于避免发生异常时的资源泄漏。它实现了专有所有权的概念,这意味着它可以确保一个对象及其关联资源一次只能由一个指针"拥有''。当此所有者被销毁或变空或开始拥有另一个对象时,先前拥有的对象也将被销毁,所有相关资源都将被释放。
唯一指针unique_ptr继承了自动指针auto_ptr(该类最初是C++ 98引入的,但现在已弃用)。 唯一指针unique_ptr提供了一个简单明了的接口,与自动指针auto_ptr相比,它更不容易出错。
函数通常以以下步骤运行:
如果获取的资源已经绑定到本地对象,则在进入时获取的资源会在函数退出时自动释放,因为函数退出时调用了这些本地对象的析构函数。但是如果资源是手动获取的,并且没有绑定到任何对象,则必须手动释放它们。使用指针时通常会手动管理资源。
以这种方式使用指针的典型示例是使用new和delete创建和销毁对象:
void f()
{
ClassA* ptr = new ClassA; //手动创建一个对象
... //执行一些操作
delete ptr; //清理:手动销毁对象
}
上面代码的一个明显问题是,对象的销毁可能会被遗忘,尤其是在函数内部有return语句的情况下。 还有一种不太明显的危险就是可能发生的异常将导致函数立即退出,而不会调用末尾的delete语句,最终导致资源泄漏。
为了避免这种资源泄漏通常要求函数捕获所有异常。 例如:
void f()
{
ClassA* ptr = new ClassA; //手动创建一个对象
try {
... //执行一些操作
}
catch (...) { //处理异常
delete ptr; //清理
throw; //重新抛出异常
}
delete ptr; //正常退出时清理
}
为了在发生异常时正确处理此对象的删除,代码变得复杂和冗余。如果以这种方式处理第二个对象,或者使用了多个捕获子句,问题将变得更加严重。这不是一种好的编程风格,应避免使用,因为它很复杂且容易出错。
自动指针unique_ptr可以解决这个问题。只要自动指针本身被销毁,它就可以释放其指向的数据。此外,由于它是一个局部变量,所以退出函数时唯一指针会自动销毁,无论退出是正常的还是由于异常导致的。
unique_ptr是一个指针,作为它所引用的对象的唯一所有者。当对象的唯一指针unique_ptr被销毁时,对象将自动销毁。对意味唯一指针unique_ptr的要求是其对象只有一个所有者。
下面是前面的示例改为使用唯一指针unique_ptr的代码:
#include
void f()
{
//创建并初始化一个unique_ptr指针
std::unique ptr(new ClassA);
... //执行一些其他操作
}
这样修改之后就不需要删除语句和catch子句。
唯一指针unique_ptr具有与普通指针几乎相同的接口;
//创建并初始化指向字符串的unique_ptr指针
std::unique_ptr up(new std::string("Tom"));
(*up)[0] = ’C’; //替换第一个字母
up->append("ming"); //追加字符串
std::cout << *up << std::endl; //打印整个字符串
但唯一指针没有定义诸如++之类的指针算法(因为指针算法是麻烦的根源)。
注意,唯一指针unique_ptr不允许使用赋值语法进行初始化,而必须使用普通指针直接初始化:
std::unique_ptr up = new int; //错误
std::unique_ptr up(new int); //正确
唯一指针unique_ptr可以为空。例如使用默认构造函数初始化或用nullptr对唯一指针unique_ptr进行赋值或调用reset()::
std::unique_ptr up;
up = nullptr;
up.reset();
另外,可以调用release()让唯一指针unique_ptr返回其拥有的对象,并放弃所有权,以便调用方对返回的对象负责:
std::unique_ptr up(new std::string("Tom"));
...
std::string* sp = up.release(); //up失去拥有权
检查唯一指针unique_ptr是否拥有对象的一些方法:
if (up) { //如果up不为空
std::cout << *up << std::endl;
}
if (up != nullptr) //如果up不为空
if (up.get() != nullptr) //如果up不为空
唯一指针unique_ptr提供排他的所有权语义,但由程序员确保同一指针不会初始化两个唯一指针unique_ptr:
std::string* sp = new std::string("hello");
std::unique_ptr up1(sp);
std::unique_ptr up2(sp); //错误:up1和up2拥有相同的数据
不幸的是,这是一个运行时错误无法在编译时发现,因此需要依靠程序员自己避免这种错误。
使用普通的复制语义无法复制或给唯一指针unique_ptr赋值,但可以使用C++ 11提供的move语义。在这种情况下,构造函数或赋值运算符会将所有权转移到另一个唯一指针unique_ptr。
例如,考虑以下拷贝构造函数的用法:
//使用一个新对象初始化一个unique_ptr
std::unique_ptr up1(new ClassA);
//复制unique_ptr
std::unique_ptr up2(up1); //错误:编译不通过
//转移unique_ptr的所有权
std::unique_ptr up3(std::move(up1)); //正确
在第一条语句之后,up1拥有使用new运算符创建的对象。 第二条语句尝试调用拷贝构造函数将导致一个编译时错误,因为up2不能成为该对象的另一个所有者。第三条语句将所有权从up1转移到up3。 因此,之后up3拥有使用new创建的对象,而up1不再拥有该对象。
赋值运算符的行为类似:
//用新对象初始化一个unique_ptr
std::unique_ptr up1(new ClassA);
std::unique_ptr up2; //创建另一个unique_ptr
up2 = up1; //错误:编译通不过
up2 = std::move(up1); //将up1的所有权转移到up2
移动赋值将所有权从up1转移到up2。如果up2在分配前拥有一个对象,则对该对象调用delete:
std::unique_ptr up1(new ClassA);
std::unique_ptr up2(new ClassA);
up2 = std::move(up1); //销毁up2关联的对象,将up1关联对象的所有权转移给up2
在没有获得新所有权的情况下失去对象所有权的唯一指针unique_ptr表示没有对象。
要将新值赋给唯一指针unique_ptr,该新值也必须是唯一指针unique_ptr而不能是普通指针:
std::unique_ptr ptr;
ptr = new ClassA; //错误
ptr = std::unique_ptr(new ClassA); //正确
up = nullptr; //正确,相当于调用reset()
所有权转移意味着函数可以使用唯一指针unique_ptr将所有权转移给其他函数。 可以有两种使用方式:
void sink(std::unique_ptr up) //形参up获得对象的所有权
{
...
}
std::unique_ptr up(new ClassA);
...
sink(std::move(up)); //up失去关联对象的所有权
...
std::unique_ptr source()
{
std::unique_ptr ptr(new ClassA);
...
return ptr; //将ptr关联对象的所有权转移给调用函数
}
void g()
{
std::unique_ptr p;
for (int i=0; i<10; ++i) {
p = source(); //p获得返回对象的所有权
}
}
每次调用source()时,它都会使用new创建一个对象,并将该对象及其所有权返回给调用者。将返回值赋给p会将所有权转移给p。
在第二遍及其他遍历循环中,对p的赋值将删除p先前拥有的对象。离开g(),从而销毁p,导致p拥有的最后一个对象的销毁。无论如何,都不会发生资源泄漏。即使抛出异常,拥有资源的任何unique_ptr也会确保删除该资源。
source()的return语句中不需要std::move()的原因是,根据C++ 11的语言规则,编译器将自动尝试移动。
通过在类中使用唯一指针unique_ptr指针可以避免资源泄漏。
如果使用唯一指针unique_ptr而不是普通的指针,则不再需要析构函数,因为对象会随着成员的删除而被删除。
此外,unique_ptr有助于避免对象初始化期间引发的异常引起的资源泄漏。因为只有在完成构造后才调用析构函数,所以如果构造函数内部发生异常,则仅针对已完全构造的对象调用析构函数。如果在构造过程中第一个new执行成功而第二个new没有成功,则可能导致具有多个原始指针的类的资源泄漏。
例如:
#include
#include
#include
#include
using namespace std;
class ClassA {
public:
ClassA(const string & sName, const string & sOwnerName, int nVal)
: m_sName(sName), m_sOwnerName(sOwnerName)
{
cout << "“" << m_sOwnerName << "”的ClassA对象“" << m_sName << "”开始构造" << endl;
if (0 == nVal)
{
runtime_error oRtEx("值不能为0\n");
throw oRtEx;
} else {
m_dVal = 1.0 / nVal;
}
cout << "“" << m_sOwnerName << "”的ClassA对象“" << m_sName << "”完成构造" << endl;
}
ClassA(const ClassA & o2BeCopy) {
m_dVal = o2BeCopy.m_dVal;
m_sName = o2BeCopy.m_sName;
m_sOwnerName = o2BeCopy.m_sOwnerName;
}
~ClassA() {
cout << "“" << m_sOwnerName << "”的ClassA对象“" << m_sName << "”析构" << endl;
}
void setOwnerName(const string & sOwnerName) { m_sOwnerName = sOwnerName; }
private:
double m_dVal;
string m_sName;
string m_sOwnerName;
};
class ClassB {
public:
//如果ptr2的初始化抛出异常将导致资源泄露
ClassB (int nVal1, int nVal2, const string & sName)
{
cout << "名为“" << sName << "”的ClassB对象开始构造" << endl;
m_ptr1 = new ClassA("m_ptr1", sName, nVal1);
m_ptr2 = new ClassA("m_ptr2", sName, nVal2);
m_sName = sName;
cout << "名为“" << sName << "”的ClassB对象完成构造" << endl;
}
//拷贝构造
//如果ptr2的初始化之前抛出异常将导致资源泄露
ClassB (const ClassB& x)
{
cout << "名为“" << m_sName << "”的ClassB对象开始拷贝构造" << endl;
m_ptr1 = new ClassA(*(x.m_ptr1));
m_ptr1->setOwnerName("拷贝构造");
ostringstream oss;
oss << "名为“" << m_sName << "”的ClassB对象拷贝构造出现异常\n";
runtime_error oRtEx(oss.str());
throw oRtEx;
m_ptr2 = new ClassA(*(x.m_ptr2));
m_ptr2->setOwnerName("拷贝构造");
cout << "名为“" << m_sName << "”的ClassB对象完成拷贝构造" << endl;
}
//赋值运算符
const ClassB& operator= (const ClassB& x) {
*m_ptr1 = *x.m_ptr1;
*m_ptr2 = *x.m_ptr2;
return *this;
}
~ClassB () {
cout << "名为“" << m_sName << "”的ClassB对象开始析构" << endl;
delete m_ptr1;
delete m_ptr2;
cout << "名为“" << m_sName << "”的ClassB对象完成析构" << endl;
}
private:
ClassA* m_ptr1; //指针成员
ClassA* m_ptr2;
string m_sName;
};
int main()
{
try {
ClassB oB(1, 0, "oB");
} catch (const exception & ex) {
cout << "ClassB oB(1, 0)执行出现异常,具体原因:" << ex.what();
}
cout << "=====================" << endl;
try {
ClassB oB1(1, 2, "oB1");
ClassB oB2(oB1);
} catch (const exception & ex) {
cout << "ClassB oB2(oB1)执行出现异常,具体原因:" << ex.what();
}
return 0;
}
运行结果如下:
名为“oB”的ClassB对象开始构造
“oB”的ClassA对象“m_ptr1”开始构造
“oB”的ClassA对象“m_ptr1”完成构造
“oB”的ClassA对象“m_ptr2”开始构造
ClassB oB(1, 0)执行出现异常,具体原因:值不能为0
=====================
名为“oB1”的ClassB对象开始构造
“oB1”的ClassA对象“m_ptr1”开始构造
“oB1”的ClassA对象“m_ptr1”完成构造
“oB1”的ClassA对象“m_ptr2”开始构造
“oB1”的ClassA对象“m_ptr2”完成构造
名为“oB1”的ClassB对象完成构造
名为“”的ClassB对象开始拷贝构造
名为“oB1”的ClassB对象开始析构
“oB1”的ClassA对象“m_ptr1”析构
“oB1”的ClassA对象“m_ptr2”析构
名为“oB1”的ClassB对象完成析构
ClassB oB2(oB1)执行出现异常,具体原因:名为“”的ClassB对象拷贝构造出现异常
为了避免这种可能的资源泄漏,可以使用唯一指针unique_ptr指针:
#include
#include
#include
#include
using namespace std;
class ClassA {
public:
ClassA(const string & sName, const string & sOwnerName, int nVal)
: m_sName(sName), m_sOwnerName(sOwnerName)
{
cout << "“" << m_sOwnerName << "”的ClassA对象“" << m_sName << "”开始构造" << endl;
if (0 == nVal)
{
runtime_error oRtEx("值不能为0\n");
throw oRtEx;
} else {
m_dVal = 1.0 / nVal;
}
cout << "“" << m_sOwnerName << "”的ClassA对象“" << m_sName << "”完成构造" << endl;
}
ClassA(const ClassA & o2BeCopy) {
m_dVal = o2BeCopy.m_dVal;
m_sName = o2BeCopy.m_sName;
m_sOwnerName = o2BeCopy.m_sOwnerName;
}
~ClassA() {
cout << "“" << m_sOwnerName << "”的ClassA对象“" << m_sName << "”析构" << endl;
}
void setOwnerName(const string & sOwnerName) { m_sOwnerName = sOwnerName; }
private:
double m_dVal;
string m_sName;
string m_sOwnerName;
};
class ClassB {
public:
//如果ptr2的初始化抛出异常将导致资源泄露
ClassB (int nVal1, int nVal2, const string & sName)
{
cout << "名为“" << sName << "”的ClassB对象开始构造" << endl;
m_ptr1 = unique_ptr(new ClassA("m_ptr1", sName, nVal1));
m_ptr2 = unique_ptr(new ClassA("m_ptr2", sName, nVal2));
m_sName = sName;
cout << "名为“" << sName << "”的ClassB对象完成构造" << endl;
}
//拷贝构造
//如果ptr2的初始化之前抛出异常将导致资源泄露
ClassB (const ClassB& x)
{
cout << "名为“" << m_sName << "”的ClassB对象开始拷贝构造" << endl;
m_ptr1 = unique_ptr(new ClassA(*(x.m_ptr1)));
m_ptr1->setOwnerName("拷贝构造");
ostringstream oss;
oss << "名为“" << m_sName << "”的ClassB对象拷贝构造出现异常\n";
runtime_error oRtEx(oss.str());
throw oRtEx;
m_ptr2 = unique_ptr(new ClassA(*(x.m_ptr2)));
m_ptr2->setOwnerName("拷贝构造");
cout << "名为“" << m_sName << "”的ClassB对象完成拷贝构造" << endl;
}
//赋值运算符
const ClassB& operator= (const ClassB& x) {
*m_ptr1 = *x.m_ptr1;
*m_ptr2 = *x.m_ptr2;
return *this;
}
private:
unique_ptr m_ptr1; //指针成员
unique_ptr m_ptr2;
string m_sName;
};
int main()
{
try {
ClassB oB(1, 0, "oB");
} catch (const exception & ex) {
cout << "ClassB oB(1, 0)执行出现异常,具体原因:" << ex.what();
}
cout << "=====================" << endl;
try {
ClassB oB1(1, 2, "oB1");
ClassB oB2(oB1);
} catch (const exception & ex) {
cout << "ClassB oB2(oB1)执行出现异常,具体原因:" << ex.what();
}
return 0;
}
运行结果如下:
名为“oB”的ClassB对象开始构造
“oB”的ClassA对象“m_ptr1”开始构造
“oB”的ClassA对象“m_ptr1”完成构造
“oB”的ClassA对象“m_ptr2”开始构造
“oB”的ClassA对象“m_ptr1”析构
ClassB oB(1, 0)执行出现异常,具体原因:值不能为0
=====================
名为“oB1”的ClassB对象开始构造
“oB1”的ClassA对象“m_ptr1”开始构造
“oB1”的ClassA对象“m_ptr1”完成构造
“oB1”的ClassA对象“m_ptr2”开始构造
“oB1”的ClassA对象“m_ptr2”完成构造
名为“oB1”的ClassB对象完成构造
名为“”的ClassB对象开始拷贝构造
“拷贝构造”的ClassA对象“m_ptr1”析构
“oB1”的ClassA对象“m_ptr2”析构
“oB1”的ClassA对象“m_ptr1”析构
ClassB oB2(oB1)执行出现异常,具体原因:名为“”的ClassB对象拷贝构造出现异常
现在可以不需要析构函数了,因为唯一指针unique_ptr会完成资源的释放。 不过使用了唯一指针unique_ptr之后就必须实现拷贝构造函数和重载赋值运算符,因为默认情况下两者都将尝试复制成员或为成员进行赋值,但由于唯一指针unique_ptr会让两者发生错误。 如果不提供它们,则ClassB也将仅提供移动语义。
默认情况下,如果唯一指针失去所有权,则对其拥有的对象调用delete。 不幸的是,由于源于C的语言规则,C++无法区分指向一个对象的指针的类型和对象的数组的类型。 但是,根据数组的语言规则,必须调用运算符delete []而不是delete。 因此,以下代码可以编译通过,但会导致运行时错误:
std::unique_ptr up(new std::string[10]); //运行时错误
幸运的是,C++标准库为唯一指针unique_ptr针对数组提供了特殊处理,当指针失去对所引用对象的所有权时,该类将对引用对象调用delete []。
因此,只需要声明:
std::unique_ptr up(new std::string[10]);
注意,唯一指针unique_ptr针对数组的接口与针对普通指针的接口略有不同——不提供运算符*和->,而是提供运算符[]来访问引用数组内的对象:
std::unique_ptr up(new std::string[10]);
...
std::cout << *up << std::endl; //错误:没有为数组定义*运算符
std::cout << up[0] << std::endl;
与普通数组的索引一样,由程序员来确保索引有效。使用无效的索引会导致未定义的行为。
还要注意,唯一指针unique_ptr不允许通过派生类型的数组进行初始化。 这反映了多态不适用于普通数组的事实。
让我们看一下unique_ptr类的声明。 从概念上讲,此类声明为以下内容:
namespace std {
//初始模板
template >
class unique_ptr
{
public:
...
T& operator*() const;
T* operator->() const noexcept;
...
};
//针对数组进行偏特化
template
class unique_ptr
{
public:
...
T& operator[](size_t i) const;
...
}
}
可以看到有一个特殊版本的唯一指针unique_ptr用于处理数组。 该版本提供了运算符[]而不是运算符*和->来处理数组而不是单个对象,但都使用类std :: default_delete<>作为删除器,它本身专门用于调用delete []而不是对数组的delete:
namespace std {
//初始模板
template
class default_delete
{
public:
void operator()(T* p) const; //调用delete p
...
};
//针对数组进行偏特化
template
class default_delete
{
public:
void operator()(T* p) const; //调用delete[] p
...
};
}
默认模板参数也自动适用于偏特化。
当唯一指针unique_ptr引用的对象销毁时需要进行除delete或delete []之外的其他操作时,必须自定义删除器。 unique_ptr定义删除器的方法与shared_ptr略有不同——必须将删除器的类型指定为第二个模板参数。
该类型可以是对函数,函数指针或函数对象的引用。如果使用了函数对象,则应声明其“函数调用操作符”()以指向该对象的指针。
例如,以下代码在删除对象之前会打印一条附加消息:
#include
#include
using namespace std;
class ClassA {};
class ClassADeleter
{
public:
void operator () (ClassA* p) {
cout << "调用ClassA对象的删除器" << std::endl;
delete p;
}
};
int main()
{
unique_ptr oUp(new ClassA());
return 0;
}
要指定函数或lambda表达式,必须将删除程序的类型声明为void(*)(T *)或std::function
std::unique_ptr up(new int[10],
[](int* p) {
...
delete[] p;
});
std::unique_ptr> up(new int[10],
[](int* p) {
...
delete[] p;
});
auto l = [](int* p) {
...
delete[] p;
};
std::unique_ptr> up(new int[10], l);
为避免在传递函数指针或lambda时指定删除器的类型,还可以使用别名模板,这是C++ 11提供的一种语言功能:
template
using uniquePtr = std::unique_ptr; //别名模板
...
uniquePtr up(new int[10], [](int* p) {//此处使用
...
delete[] p;
});
这样使用与shared_ptrs大致相同的接口来指定删除器。
这是使用自定义删除器的完整示例:
#include
#include
#include
#include
#include
#include
using namespace std;
class DirCloser
{
public:
void operator () (DIR* dp) {
if (closedir(dp) != 0) {
std::cerr << "closedir()失败" << std::endl;
}
}
};
int main()
{
//打开当前目录
unique_ptr pDir(opendir("."));
//处理目录中每个项目(文件或目录)
struct dirent *dp;
while ((dp = readdir(pDir.get())) != nullptr) {
string filename(dp->d_name);
cout << "处理" << filename << endl;
}
}
在main()内部使用opendir(),readdir()和closedir()的标准POSIX接口处理当前目录的条目。 为了确保在任何情况下都由closedir()关闭打开的目录,定义了一个unique_ptr,每当引用打开目录的句柄被销毁时,都会导致DirCloser被调用。 唯一指针的删除器可能不会抛出异常。 因此,仅打印错误消息。
使用unique_ptr的另一个优点是不可复制。 请注意,readdir()并非无状态,因此最好确保在使用句柄处理目录时,句柄的副本不能修改其状态。
如果您不想处理closedir()的返回值,也可以传递closedir()直接作为函数指针,指定删除器为函数指针。
unique_ptr pDir(opendir("."), closedir); //可能失效
注意以上代码不能保证可移植,因为closedir具有外部的“C”链接,因此在C++代码中,不能保证将其转换为int(*)(DIR *)。 对于可移植代码,需要定义下面的中间类型:
extern "C" typedef int(*DIRDeleter)(DIR*);
unique_ptr pDir(opendir("."), closedir); //正确
closedir()返回一个int,因此必须指定int(*)(DIR *)作为Deleter的类型。 注意,通过函数指针进行的调用是间接调用,很难进行优化。
unique_ptr具有专有所有权的概念——当其拥有排他控制权后,程序将无法创建多个unique_ptr拥有相同的关联对象。
unique_ptr的主要目标是确保在指针生命周期结束时删除关联的对象(或清理其资源)。这尤其有助于提供异常安全性。 与共享指针shared_ptr相反,此类的重点是最小的空间和时间开销。
对类unique_ptr<>进行模板化,以指定初始指针所指向的对象的类型及其变量:
namespace std {
template >
class unique_ptr
{
public:
typedef ... pointer; //可能是D::pointer
typedef T element_type;
typedef D deleter_type;
...
};
}
提供了数组的偏特化(根据语言规则,它具有相同的默认变量,即default_delete
namespace std {
template
class unique_ptr
{
public:
typedef ... pointer; //可能是D::pointer
typedef T element_type;
typedef D deleter_type;
...
};
}
元素类型T可能为void,因此unique_ptr拥有一个类型未指定的对象,就像void *。还要注意,虽然定义了pointer类型,但其不一定为T *。如果删除程序D定义了pointer类型,则将使用此类型。在这种情况下,模板参数T仅具有类型标签的作用,因为在类unique_ptr <>中没有成员依赖于T;一切都取决于pointer。这样做的优点是unique_ptr可以容纳其他智能指针。
下表列出了为唯一指针提供的所有操作。
操作 | 结果 |
unique_ptr<...> up | 默认构造函数;使用默认/传递的删除器类型的实例作为删除器,创建一个空的唯一指针 |
unique_ptr |
使用默认/传递的删除器类型的实例作为删除器,创建一个空的唯一指针 |
unique_ptr<...> up(ptr) | 使用默认/传递的删除器类型的实例作为删除器,创建拥有* ptr的唯一指针 |
unique_ptr<...> up(ptr,del) | 使用del作为删除器创建拥有* ptr的唯一指针 |
unique_ptr |
创建一个拥有up2先前拥有的指针的唯一指针(此后up2为空) |
unique_ptr |
创建一个拥有先前由auto_ptr ap拥有的指针的唯一指针(此后ap为空) |
up.~unique_ptr() | 析构函数;调用拥有者对象的删除器 |
up = move(up2) | 移动赋值(up2将所有权转移到up) |
up = nullptr | 调用拥有者对象的删除器,并使其为空(等同于up.reset()) |
up1.swap(up2) | 交换up1和up2的指针和删除器 |
swap(up1,up2) | 交换up1和up2的指针和删除器 |
up.reset() | 调用拥有者对象的删除器,并使其为空(相当于up = nullptr) |
up.reset(ptr) | 调用拥有者对象的删除器,并将共享指针重新初始化为自己的* ptr |
up.release() | 将所有权放弃给调用者(不调用删除器就返回拥有的对象) |
up.get() | 返回存储的指针(拥有的对象的地址;如果没有,则返回nullptr) |
*up | 仅单个对象;返回拥有的对象(如果没有,则为未定义的行为) |
up->... | 仅单个对象;提供拥有对象的成员访问权限(如果没有,则为未定义的行为) |
up[idx] | 仅数组对象;返回具有存储数组的索引idx的元素(如果没有,则为未定义的行为) |
if (up) | 运算符bool();返回up是否为空 |
up1 == up2 | 对存储的指针调用==(可以为nullptr) |
up1 != up2 | 对存储的指针调用!=(可以为nullptr) |
up1 < up2 | 对存储的指针调用<(可以为nullptr) |
up1 <= up2 | 对存储的指针调用<=(可以为nullptr) |
up1 > up2 | 对存储的指针调用>(可以为nullptr) |
up1 >= up2 | 对存储的指针调用>=(可以为nullptr) |
up.get_deleter() | 返回删除器的引用 |
对于不同类型,以指针和删除器为参数的构造函数已重载,因此指定了以下行为:
D d; //创建删除器对象
unique_ptr p1(new int, D()); //D必须支持移动构造
unique_ptr p2(new int, d); //D必须支持拷贝构造
unique_ptr p3(new int, d); //p3保持一个对d的引用
unique_ptr p4(new int, D()); //错误:右值删除器对象不能具有引用删除器类型
对于单个对象,移动构造函数和赋值运算符是成员模板,因此可以进行类型转换。所有比较运算符都针对不同的元素和变量类型进行了模板化。
所有比较运算符都会调用unique_ptr指针内部使用的原始指针相应的比较运算符(相当于对get()返回的值调用相同的运算符)。它们都将nullptr作为参数进行重载,因此,您可以检查是否存在有效的指针,甚至可以检查原始指针是否小于或大于nullptr。
与单对象接口相比,数组类型的偏特化接口具有以下差异: