C++11智能指针:unique_ptr
unique_ptr
1.概念
unique_ptr形如其名,与所指对象的内存紧密地绑定,不能与其他的unique_ptr类型的指针对象共享所指向对象的内存。
在cplusplus.com中,unique_ptr声明如下:
// non-specialized
template <class T, class D = default_delete<T>> class unique_ptr;
// array specialization
template <class T, class D> class unique_ptr<T[],D>;
是一个模版类,T指得是指向内存的类型,D指得是deleter类型,默认为default_deleter。
请看如下例子:
int main(int argc, char* argv[]) {
std::unique_ptr<int> u1(new int(1));
std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
std::unique_ptr<int> u2 = u1; //编译出错
return 0;
}
编译结果如下:
从编译log来看,use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’
,具体原因是unique_ptr不允许与其他对象共享所指向对象的内存,已经删除了拷贝构造函数,无法进行拷贝操作。
将上述代码改为如下形式编译成功:
int main(int argc, char* argv[]) {
std::unique_ptr<int> u1(new int(1));
std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
std::unique_ptr<int> u2 = move(u1);
std::cout << "u2 value : " << *u2 << '\n' << " addredd : " << u2.get() << std::endl;
return 0;
}
执行结果如下:
从结果可以看出u2所指向的值为1,u2所指向的地址与u1所指向的地址相同(通过get成员函数可以获取所指向的地址)。这是由于使用了move操作,u1把内存的所有权释放,u2获取到内存的所有权,所以u2所指向的地址和u1所指向的地址相同。此时如果再次打印u1的值和所指向的内存地址,代码如下:
int main(int argc, char* argv[]) {
std::unique_ptr<int> u1(new int(1));
std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
std::unique_ptr<int> u2 = move(u1);
std::cout << "u2 value : " << *u2 << '\n' << " addredd : " << u2.get() << std::endl;
std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
return 0;
}
执行结果如下:
出现Segmentation fault的原因是u1此时以及把所指内存的所有权转给u2,但还是打印了u1所指的值和地址。在此我就不进行core file分析了。
2.用法
1.构造函数
unique_ptr一共有8个构造函数,分别是:
描述 | 函数原型 |
---|---|
1.默认构造函数 | constexpr unique_ptr() noexcept; |
2.初始化为空指针 | constexpr unique_ptr (nullptr_t) noexcept : unique_ptr() {} |
3.初始化为非空指针 | explicit unique_ptr (pointer p) noexcept; |
4.初始化为非空指针+左值deleter | unique_ptr (pointer p,typename |
5.初始化为非空指针+右值deleter | unique_ptr (pointer p,typename remove_reference |
6.移动构造函数 | unique_ptr (unique_ptr&& x) noexcept; |
7.非默认deleter的移动构造 | template <class U, class E> unique_ptr (unique_ptr<U,E>&& x) noexcept; |
8.从auto_ptr类型移动(C++17中移除掉这种用法了) | template |
9.拷贝构造函数(已删除,不可使用 | unique_ptr (const unique_ptr&) = delete; |
下面我们分别测试一下每种构造函数的用法:
#include <memory>
#include <iostream>
using namespace std;
class smart_point_class {
public:
smart_point_class(int i) : _i(i) {
cout << "Default construct..." << _i << endl;
}
~smart_point_class() {
cout << "Destroct..." << _i << endl;
}
inline int get_value() {
return _i;
}
private:
smart_point_class(const smart_point_class&) = delete;
smart_point_class(smart_point_class&&) = delete;
private:
int _i;
};
class deleter {
public:
deleter() = default;
deleter(const deleter& other) {
cout << "Copy deleter." << endl;
}
deleter(deleter&& other) {
cout << "Move deleter." << endl;
}
deleter& operator=(const deleter&) {
cout << "Copy assign deleter." << endl;
return *this;
}
deleter& operator=(deleter&&) {
cout << "Move assign deleter." << endl;
return *this;
}
void operator() (smart_point_class* spc) {
cout << "Start of deleter function : " << spc->get_value() << endl;
delete spc;
spc = nullptr;
cout << "End of deleter function." << endl;
}
};
void test_construct_fun() {
cout << "C++ unique_ptr test function :" << endl;
cout << "----------------------------------------------------" << endl;
cout << "u1" << endl;
unique_ptr<smart_point_class> u1;
cout << "u1 : " << (u1? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u2" << endl;
unique_ptr<smart_point_class> u2(nullptr);
cout << "u2 : " << (u2? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u3" << endl;
unique_ptr<smart_point_class> u3(new smart_point_class(3));
cout << "u3 : " << (u3? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u4" << endl;
deleter d;
unique_ptr<smart_point_class, decltype(d)> u4(new smart_point_class(4), d);
cout << "u4 : " << (u4? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u5" << endl;
unique_ptr<smart_point_class, deleter> u5(new smart_point_class(5), deleter());
cout << "u5 : " << (u5? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u6" << endl;
unique_ptr<smart_point_class> u6 = move(u3);
cout << "u6 : " << (u6? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u7" << endl;
unique_ptr<smart_point_class, deleter> u7 = move(u5);
cout << "u7 : " << (u7? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "u8" << endl;
unique_ptr<smart_point_class> u8 (auto_ptr<smart_point_class>(new smart_point_class(8)));
cout << "u8 : " << (u8? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
cout << "Test finish..." << endl;
cout << "u1 : " << (u1? "not null" : "null") << '\n';
cout << "u2 : " << (u2? "not null" : "null") << '\n';
cout << "u3 : " << (u3? "not null" : "null") << '\n';
cout << "u4 : " << (u4? "not null" : "null") << '\n';
cout << "u5 : " << (u5? "not null" : "null") << '\n';
cout << "u6 : " << (u6? "not null" : "null") << '\n';
cout << "u7 : " << (u7? "not null" : "null") << '\n';
cout << "u8 : " << (u8? "not null" : "null") << '\n';
cout << "----------------------------------------------------" << endl;
}
int main(int argc, char* argv[]) {
test_construct_fun();
return 0;
}
编译结果如下:
会发现有个警告,是因为在C++11中不再建议使用auto_ptr了,但既然构造函数中有这个用法,我们就在这里测试一下(在C++17中已经移除掉这种用法了)。
这段代码执行结果如下:
现在我们来分析一下执行结果:
- u1使用了默认构造函数,所指向内容为空;
- u2使用了初始化为空指针的构造函数,所指向内容为空;
- u3使用了初始化为非空指针的构造函数,所指向内容不为空;
- u4使用了初始化为非空指针+左值deleter的构造函数,会线构造一个deleter,然后拷贝这个deleter,所指向内容不为空;
- u5使用了初始化为非空指针+右值deleter的构造函数,会线构造一个deleter,然后移动这个deleter,所指向内容不为空;
- u6使用了移动构造函数,u3将所有权转到了u6,u6不为空,但u3变为空;
- u7使用了自定义deleter类型的移动构造函数,u5将所有权转到了u7,u7不为空,但u5变为空;
- u8使用了从auto_ptr类型移动的构造函数,不为空,但C++17已经移除了这种用法,不再建议使用;
- 在函数体内所有语句执行完毕之后,释放资源。其中使用默认deleter的会调用smart_pointer_class的析构函数,不使用默认deleter的会调用deleter函数/仿函数。
2.析构函数
没有需要特殊说明的。
3.赋值重载操作
unique_ptr一共有3个赋值重载操作,分别是:
描述 | 函数原型 |
---|---|
1.移动赋值 | unique_ptr& operator= (unique_ptr&& x) noexcept; |
2.赋值为空 | unique_ptr& operator= (nullptr_t) noexcept; |
3.赋值非默认deleter的unique_ptr | template <class U, class E> unique_ptr& operator= (unique_ptr<U,E>&& x) noexcept; |
4.拷贝赋值函数(已删除,不可使用) | unique_ptr& operator= (const unique_ptr&) = delete; |
下面我们分别测试一下每种赋值函数的用法:
#include <memory>
#include <iostream>
using namespace std;
class smart_point_class {
public:
smart_point_class(int i) : _i(i) {
cout << "Default construct..." << _i << endl;
}
~smart_point_class() {
cout << "Destroct..." << _i << endl;
}
inline int get_value() {
return _i;
}
private:
smart_point_class(const smart_point_class&) = delete;
smart_point_class(smart_point_class&&) = delete;
private:
int _i;
};
class deleter {
public:
deleter() = default;
deleter(const deleter& other) {
cout << "Copy deleter." << endl;
}
deleter(deleter&& other) {
cout << "Move deleter." << endl;
}
deleter& operator=(const deleter&) {
cout << "Copy assign deleter." << endl;
return *this;
}
deleter& operator=(deleter&&) {
cout << "Move assign deleter." << endl;
return *this;
}
void operator() (smart_point_class* spc) {
cout << "Start of deleter function : " << spc->get_value() << endl;
delete spc;
spc = nullptr;
cout << "End of deleter function." << endl;
}
};
void test_assign_fun() {
cout << "C++ unique_ptr assign test :" << endl;
cout << "u_base" << endl;
unique_ptr<smart_point_class> u_base(new smart_point_class(1));
cout << "u_base value : " << u_base->get_value() << '\n' << " addredd : " << u_base.get() << endl;
cout << "----------------------------------------------------" << endl;
cout << "u1" << endl;
unique_ptr<smart_point_class> u1;
u1 = move(u_base);
cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
cout << "----------------------------------------------------" << endl;
unique_ptr<smart_point_class> u2(new smart_point_class(2));
u2 = nullptr;
cout << "----------------------------------------------------" << endl;
unique_ptr<smart_point_class, deleter> u4;
u4 = unique_ptr<smart_point_class, deleter>(new smart_point_class(3));
cout << "----------------------------------------------------" << endl;
cout << "Test finish..." << endl;
}
int main(int argc, char* argv[]) {
test_assign_fun();
return 0;
}
上述代码执行结果如下:
现在我们来分析一下执行结果:
- u1从u_base处获取所有权;
- u2使用了初始化为非空指针的构造函数,所指向内容不为空,然后赋值为空,
u2 = nullptr
,等同于u2.reset()
,(在后面会具体说到reset的使用方法),在此进行析构操作; - u3首先指向空,然后用右值赋值,调用了unique_ptr的移动赋值函数;
- 所有语句执行完成之后,释放资源。u3会调用deleter函数/仿函数,u2在之前已经释放,最后释放u1,用默认的deleter。
4.get()
get函数会返回存储的指针。如果由unique_ptr不为空,则存储的指针指向由unique_ptr管理的对象,否则指向nullptr。需要注意的是调用这个函数并不会释放指针的所有权(它仍然负责删除管理的数据)。因此,此函数的返回值不用于构建新的指针。如果要获取存储的指针并释放所有权,调用unique_ptr::release()。
get函数使用方法如下:
int main(int argc, char* argv[]) {
int *i = new int(1);
cout << "i : " << i << endl;
unique_ptr<int> u(i);
cout << "u value : " << *u << '\n' << "u address : " << u.get() << endl;
return 0;
}
该段代码执行结果如下:
从图中看出可以正确打印出地址。
5.get_deleter()
get_deleter函数会返回deleter。deleter是一个可调用的对象。使用成员类型指针的单个参数对该对象的函数调用将删除托管对象,并在unique_ptr本身被销毁、分配了一个新值或在非空时重置时自动调用。unique_ptr模板使用的默认deleter类型是default_delete,一个无状态类。
get_deleter使用例子:
int main(int agrc, char* argv[]) {
// smart_point_class和deleter在之前的代码中有定义
unique_ptr<smart_point_class, deleter> u(new smart_point_class(1), deleter());
smart_point_class* p(new smart_point_class(2));
cout << "----------------------------------------------------" << endl;
u.get_deleter()(p);
cout << "----------------------------------------------------" << endl;
}
上述代码执行结果如下:
在两条分割线之间的语句u.get_deleter()(p)
,就等同于调用deleter函数/仿函数。
6.bool操作重载
没有需要特殊说明的。
7.release()
该操作会释放指针的所有权,并通过返回值返回。此调用不会销毁托管对象,但unique_ptr对象将从删除对象的责任中释放。某些其他实体必须负责删除对象。
请看如下代码:
int main(int agrc, char* argv[]) {
unique_ptr<smart_point_class, deleter> u(new smart_point_class(1), deleter());
u.release();
}
上述代码执行结果如下:
会发现并没有对u存储的指针进行销毁的操作,这样非常容易造成内存泄漏。代码应该改为如下形式:
int main(int agrc, char* argv[]) {
unique_ptr<smart_point_class, deleter> u(new smart_point_class(1), deleter());
smart_point_class *p = u.release();
delete p;
}
上述代码执行结果如下:
8.reset()
销毁当前由unique_ptr管理的对象(如果有的话),并获取p的所有权。如果p是一个空指针(例如默认初始化的指针),unique_ptr将变为空,在调用之后不管理任何对象。若要释放存储指针的所有权而不破坏它,请使用成员函数release。
release使用例子:
int main(int agrc, char* argv[]) {
unique_ptr<smart_point_class> u1(new smart_point_class(1));
cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
smart_point_class *p (new smart_point_class(2));
cout << "----------------------------------------------------" << endl;
u1.reset(p);
cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
cout << "----------------------------------------------------" << endl;
u1.reset();
cout << "----------------------------------------------------" << endl;
}
上述代码执行结果如下:
- 在执行完
u1.reset(p)
语句之后,会首先销毁资源,然后u1所管理内存的value由1变为2,对应的地址也有了改变; - 在执行完
u1.reset()
语句之后,会销毁u1现在所管理内存的资源。
9.swap()
交换两个unique_ptr所管理的对象。
swap使用例子:
int main(int agrc, char* argv[]) {
unique_ptr<smart_point_class> u1(new smart_point_class(1));
unique_ptr<smart_point_class> u2(new smart_point_class(2));
cout << "----------------------------------------------------" << endl;
cout << "Before swap :" << endl;
cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
cout << "u2 value : " << u2->get_value() << '\n' << " addredd : " << u2.get() << endl;
cout << "----------------------------------------------------" << endl;
u1.swap(u2);
cout << "After swap :" << endl;
cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
cout << "u2 value : " << u2->get_value() << '\n' << " addredd : " << u2.get() << endl;
cout << "----------------------------------------------------" << endl;
}
上述代码执行结果如下:
可以看到u1和u2所管理的对象做了交换操作。
10. * ->操作
与普通指针操作相同,在此不再赘述。