unique_ptr通过指针占有并管理另一个对象(主要负责管理对象的销毁,或者说生命周期)。当unique_ptr被销毁时或者给unique_ptr赋值另一个对象的指针时,unique_ptr将删除原来管理的对象。unique_ptr可以管理单个对象,也可以管理动态分配的对象数组。unique_ptr类满足可移动构造以及可移动赋值,但是不满足可复制构造和可复制赋值的要求。
代码库:
https://gitee.com/gamestorm577/CppStd
创建unique_ptr
一般通过std::make_unique来创建一个unique_ptr。
例如创建一个管理单个类对象的unique_ptr:
struct MyStruct
{
MyStruct()
{
printf("MyStruct1, {X: %d, Y: %d}\n", X, Y);
}
MyStruct(int x, int y)
: X(x)
, Y(y)
{
printf("MyStruct2, {X: %d, Y: %d}\n", X, Y);
}
int X = 0;
int Y = 0;
};
std::cout << "construct a unique_ptr of single object: " << std::endl;
std::unique_ptr<MyStruct> ptr1 = std::make_unique<MyStruct>();
std::cout << "construct a unique_ptr of single object: " << std::endl;
std::unique_ptr<MyStruct> ptr2 = std::make_unique<MyStruct>(3, 5);
终端输出结果为:
construct a unique_ptr of single object:
MyStruct1, {X: 0, Y: 0}
construct a unique_ptr of single object:
MyStruct2, {X: 3, Y: 5}
也可以创建一个管理动态分配的对象数组的unique_ptr:
std::cout << "construct a unique_ptr of object array: " << std::endl;
std::unique_ptr<MyStruct[]> ptr3 = std::make_unique<MyStruct[]>(3);
终端输出结果为:
construct a unique_ptr of object array:
MyStruct1, {X: 0, Y: 0}
MyStruct1, {X: 0, Y: 0}
MyStruct1, {X: 0, Y: 0}
对象生命周期管理
智能指针的主要功能是管理对象的生命周期。对于一个动态分配的对象,当我们不需要时,需要手动删除这个对象,这种手动管理的方式非常容易犯错。unique_ptr是一个栈上的变量,它的内存申请和回收都是系统自动完成的。通过unique_ptr来管理动态分配的对象的生命周期,当unique_ptr被销毁时,自动删除其占用的对象,这样更智能,不容易犯错。
例如,当unique_ptr离开作用域时,unique_ptr被销毁,unique_ptr管理的对象被自动删除:
struct MyStruct
{
~MyStruct()
{
printf("~MyStruct\n");
}
};
{
std::unique_ptr<MyStruct> ptr = std::make_unique<MyStruct>();
printf("About to leave scope\n");
}
printf("Enter new scope\n");
终端输出结果为:
About to leave scope
~MyStruct
Enter new scope
unique_ptr也是异常安全的,当unique_ptr离开作用域前抛出异常时,也能正常删除占有的对象:
struct MyStruct
{
~MyStruct()
{
printf("~MyStruct\n");
}
};
try
{
std::unique_ptr<MyStruct> ptr = std::make_unique<MyStruct>();
printf("About to throw\n");
throw std::exception();
printf("After to throw\n");
}
catch (std::exception e)
{
printf("Catch\n");
}
终端输出结果为:
About to throw
~MyStruct
Catch
在ptr离开作用域前就抛出了异常,但是在进入catch之前Mystruct的析构函数还是被正常调用了
自定义Deleter
unique_ptr也可以自定义Deleter
struct MyStruct
{
~MyStruct()
{
printf("~MyStruct\n");
}
};
struct Deleter
{
void operator()(MyStruct* p) const
{
printf("Deleter\n");
delete p;
}
};
{
printf("ptr1: \n");
std::unique_ptr<MyStruct> ptr1 = std::make_unique<MyStruct>();
}
{
printf("ptr2: \n");
std::unique_ptr<MyStruct, Deleter> ptr2(new MyStruct);
}
终端输出结果为:
ptr1:
~MyStruct
ptr2:
Deleter
~MyStruct
访问对象
当unique_ptr管理的是单个对象时,可以使用重载操作符 operator* 来获取管理的对象,或者通过重载操作符 operator-> 来直接访问对象的成员:
struct MyStruct
{
void Test()
{
printf("MyStruct::Test\n");
}
};
printf("single object\n");
std::unique_ptr<MyStruct> ptr1 = std::make_unique<MyStruct>();
MyStruct& s1 = *ptr1;
s1.Test();
ptr1->Test();
输出结果为:
single object
MyStruct::Test
MyStruct::Test
当unique_ptr管理的是对象数组时,可以通过重载操作符 operator[] 来获取管理的对象:
printf("object array\n");
std::unique_ptr<MyStruct[]> ptr2 = std::make_unique<MyStruct[]>(5);
for (int i = 0; i < 5; ++i)
{
MyStruct& s = ptr2[i];
s.Test();
}
输出结果为:
object array
MyStruct::Test
MyStruct::Test
MyStruct::Test
MyStruct::Test
MyStruct::Test
其他接口
移动赋值函数
通过移动赋值函数,可以将对象的所有权从一个unique_ptr移交给另一个unique_ptr。对象的生命周期也从跟随原unique_ptr到跟随目标unique_ptr。例如:
struct MyStruct
{
MyStruct(int id)
: ID(id)
{
printf("MyStruct, id = %d\n", ID);
}
~MyStruct()
{
printf("~MyStruct, id = %d\n", ID);
}
int ID = 0;
};
{
printf("test1: \n");
printf("Enter outer scope\n");
std::unique_ptr<MyStruct> ptr1 = nullptr;
{
printf("Enter inner scope\n");
std::unique_ptr<MyStruct> ptr2 = std::make_unique<MyStruct>(1);
ptr1 = std::move(ptr2);
printf("Leave inner scope\n");
}
printf("Leave outer scope\n");
}
输出结果为:
test1:
Enter outer scope
Enter inner scope
MyStruct, id = 1
Leave inner scope
Leave outer scope
~MyStruct, id = 1
ptr2将对象的所有权移交给ptr1,当ptr2的作用域结束时,对象并没有被删除,而只有当ptr1的作用域结束时,对象才被删除。
当被移交的目标unique_ptr不为空时,目标unique_ptr原有的对象会被删除。例如:
{
printf("test2: \n");
std::unique_ptr<MyStruct> ptr1 = std::make_unique<MyStruct>(1);
std::unique_ptr<MyStruct> ptr2 = std::make_unique<MyStruct>(5);
printf("start move ptr2 to ptr1\n");
ptr1 = std::move(ptr2);
printf("end move ptr2 to ptr1\n");
}
输出结果为:
MyStruct, id = 1
MyStruct, id = 5
start move ptr2 to ptr1
~MyStruct, id = 1
end move ptr2 to ptr1
~MyStruct, id = 5
当ptr2将对象移交给ptr1时,ptr1的作用域并没有结束,但是ptr1只能占有一个对象,所以原来占有的对象将被删除。
get
返回管理对象的指针,例如:
std::unique_ptr<double> ptr = std::make_unique<double>(20.5);
double* val = ptr.get();
printf("number = %.3f\n", *val);
输出结果为:
number = 20.500
Program ended with exit code: 0
get_deleter
获取unique_ptr的deleter,例如:
struct MyStruct
{
};
struct Deleter
{
void Test()
{
printf("Deleter::Test\n");
}
void operator()(MyStruct* p) const
{
delete p;
}
};
std::unique_ptr<MyStruct, Deleter> ptr(new MyStruct);
auto& del = ptr.get_deleter();
del.Test();
输出结果为:
Deleter::Test
operator bool
判断unique_ptr是否占有对象,例如:
auto Func = [](const std::unique_ptr<int>& ptr) -> void
{
if (ptr)
{
printf("ptr not empty\n");
}
else
{
printf("ptr empty\n");
}
};
std::unique_ptr<int> ptr1 = std::make_unique<int>();
std::unique_ptr<int> ptr2 = nullptr;
Func(ptr1);
Func(ptr2);
输出结果为:
ptr not empty
ptr empty
release
unique_ptr不再占用管理的对象并返回该对象的指针,如果管理的是空对象则返回nullptr,例如:
struct MyStruct
{
~MyStruct()
{
printf("~MyStruct\n");
}
};
{
std::unique_ptr<MyStruct> ptr = std::make_unique<MyStruct>();
MyStruct* tmp = ptr.release();
printf("About to enter outer scope\n");
}
printf("Enter outer scope\n");
输出结果为:
About to enter outer scope
Enter outer scope
调用release接口后,ptr不再占用对象,也不再负责对象的生命周期,所以当ptr离开作用域时,并没有删除对象。
reset
删除旧的对象,替换新的对象,例如:
struct MyStruct
{
MyStruct(int id)
: ID(id)
{
}
~MyStruct()
{
printf("~MyStruct, id = %d\n", ID);
}
int ID = 0;
};
MyStruct* tmp = new MyStruct(20);
std::unique_ptr<MyStruct> ptr = std::make_unique<MyStruct>(5);
ptr.reset(tmp);
printf("finish reset\n");
输出结果为:
~MyStruct, id = 5
finish reset
~MyStruct, id = 20
Program ended with exit code: 0
swap
交换两个unique_ptr管理的对象,例如:
struct MyStruct
{
MyStruct(int id)
: ID(id)
{
}
void Test()
{
printf("Test, id = %d\n", ID);
}
int ID = 0;
};
std::unique_ptr<MyStruct> ptr1 = std::make_unique<MyStruct>(5);
std::unique_ptr<MyStruct> ptr2 = std::make_unique<MyStruct>(20);
ptr1->Test();
ptr2->Test();
ptr1.swap(ptr2);
ptr1->Test();
ptr2->Test();
输出结果为:
Test, id = 5
Test, id = 20
Test, id = 20
Test, id = 5
operator<<
输出unique_ptr管理对象的指针的值到输出流中,例如:
std::unique_ptr<int> ptr = std::make_unique<int>(5);
std::cout << ptr << std::endl;
0x600000008040
unique_ptr的比较
==、!=、<、<=、>、>=、<=>等符号可用于两个unique_ptr或者unique_ptr与nullptr的比较,比较的值是unique_ptr管理对象的指针的值,例如:
std::unique_ptr<int> ptr1 = std::make_unique<int>(5);
std::unique_ptr<int> ptr2 = std::make_unique<int>(5);
std::cout << "ptr1 == ptr1: " << (ptr1 == ptr1) << std::endl;
std::cout << "ptr1 == ptr2: " << (ptr1 == ptr2) << std::endl;
hash
计算对象的散列值,例如:
struct MyStruct
{
};
MyStruct* tmp = new MyStruct;
std::unique_ptr<MyStruct> ptr(tmp);
std::cout << std::hash<std::unique_ptr<MyStruct>>()(ptr) << std::endl;
std::cout << std::hash<MyStruct*>()(tmp) << std::endl;
输出结果为:
12022488372033534701
12022488372033534701