文章目录
c++智能指针介绍之unique_ptr
- c++11引入,头文件在
<memory>
,单一所有权,用来替换c++98引入的auto_ptr
特性:
1.1 不支持复制赋值左值操作,支持显示的move操作。
- 函数返回可以返回右值,也可以临时左值变量(例外:拷贝构造),当返回临时值时,返回值优化会调用其支持的move操作
为什么函数可以返回unique_ptr
release
,reset
,operater=(unique_ptr&& r), get
unique_ptr& operator=( unique_ptr&& r ) noexcept;
// operator=(unque_ptr &&r) // 一般用在函数返回值
unique_ptr<int> GetAge()
{
unique_ptr<int> aTmp(new int(3));
return std::move(aTmp); // 例外,这里直接返回aTmp也可以,返回值优化的时候会调用move
}
unique_ptr<int> age = GetAge();
unique_ptr<int> ageTmp = age; // 错误,不能复制
unique_ptr<int> ageTmp(age); // 错误,不能复制
unique_ptr<int> ageTmp(age.release()); // 正确,age转移所有权给ageTmp
int *ageTmpPtr = ageTmp.release(); // 返回原始指针
*ageTmpPtr = 23;
ageTmp.reset(ageTmpPtr);
ageTmpPtr = ageTmp.get();
*ageTmpPtr = 24;
ageTmp.reset(ageTmpPtr);
1.2 可以自定义删除器
- 默认使用
delete
删除器,如果unique_ptr
声明为数组,需要显示加[].unique_ptr<T[]>
,这样会默认调用delete[]
- 为
unique_ptr
添加删除器时,定义的时候需要指定删除器类型,初始化的时候需要指定删除器实例 - 普通函数作为删除器时
decltype
返回的是函数类型,需要加个*表示函数指针;lambda
表达式作为删除器时decltype
返回的是函数指针类型。 - 默认情况下
unique_ptr
和裸指针有着相同的尺寸,效率和裸指针相同; 当自定义删除器为普通函数时,尺寸至少要多一个函数指针大小; 当使用lambda
表达式作为删除器的时候并且lambda
表达式没有捕获内容时(无状态函数对象),不会增加存储空间。
unique_ptr(pointer p, const A& d) noexcept;
unique_ptr(pointer p, A& d) noexcept;
unique_ptr(pointer p, A&& d);
// 使用自定义删除器的时候,声明和赋值的地方都要传入删除器类型
class Student {
public:
Student(int a, string str) : age(a), name(str) {};
int age;
string name;
void print() { cout << "age:" << age << ", name= " << name << endl;}
};
void deleteStudent(Student *stu)
{
cout << "使用自定义删除" << endl;
delete stu;
}
int main()
{
{ // unique_ptr定义的时候要指明类型且要加上*表示函数指针类型,赋初值的时候要传入实际的函数指针
unique_ptr<Student, decltype(deleteStudent)*> up(new Student(23, "xxx"), deleteStudent);
// lambda表达式的删除器,返回函数指针类型,直接decltype
auto autoDel = [](Student *stu) {
cout << "使用lambda表达式删除器" << endl;
delete stu;
};
unique_ptr<Student, decltype(autoDel)> up2(new Student(22, "yyy"), autoDel);
// 保存数组对象
auto autoDelArr = [](Student *stu) {
cout << "使用lambda表达式删除器" << endl;
delete []stu;
};
unique_ptr<Student[], decltype(autoDelArr )> up3Arr(new Student[3]{ Student(33, "xx"), Student(33, "xx"), Student(33, "xx") }, autoDelArr );
}
return 0;
}
1.3 make_unique
- c++14 新增特性,代替直接new对象操作;
- make_unique和new的区别
make_unique
只做一次内存分配,异常安全,使用括号完美转发,对initializer_list
初始化时需要在封装一次new
做两次内存分配(分别是new对象本身和智能指针本身),可以自定义指定删除器
template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&&... params) // 使用小括号实现的完美转发
{
return std::unique_ptr<T>(new T(std::forward<Ts>(param)...));
}
unique_ptr<int> up = make_unique<int>(1);
auto spv = std::make_unique<std::vector<int>>({10, 20}); // 错误,make_unique使用()完美转发,不支持直接用{}
auto initList = {10, 20};
auto spv = std::make_unique<std::vector<int>>(initList);
与auto_ptr之间的区别:
2.1 所有权的转移方式
unique_pt
r显示的将控制权转移(move)给另一个对象后将自己设置为nullptr
,auto_ptr
拷贝(copy)也会对原始指针的控制权发生转移。
2.2 unique_ptr可以作为容器元素
- 其实也可以直接向容器中
push_back
auto_ptr
的右值对象(push_back支持右值和const左值),为什么说不能auto_ptr
对象不能作为容器元素呢,其实是容器元素必须具备拷贝构造和可赋值,而auto_ptr
对象进行拷贝和赋值时,不仅将元素本身拷贝到了目标对象,同时自己也释放了资源所有权,这不符合容器赋值的条件,此时容器中会有NULL
出现。
// push_back原型
void push_back(value_type&& __x) { emplace_back(std::move(__x)); }
void push_back(const value_type& __x) {
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);
++this->_M_impl._M_finish;
} else {
_M_realloc_insert(end(), __x);
}
}
int *p = new int(23);
auto_ptr<int> ap(p);
unique_ptr<int> up(p);
std::vector<auto_ptr<int>> vap;
std::vector<unique_ptr<int>> vup;
vup.push_back(std::move(up));
// vap.push_back(static_cast<const std::auto_ptr<int>>(ap)); // push_back参数是const的
vap.push_back(std::move(ap));
for(auto ap = vup.begin(); ap != vup.end(); ap++) { // 不支持范围for,范围for需要拷贝支持, 但引用范围for支持for(auto &ap : vup)
cout << *(*ap) << endl;
}
for(auto ap : vap) {
cout << *ap << endl;
}
// 也可以释放所有权作为构造函数构造后传递给容器
vap.emplace_back(ap.release());
2.3 unique_ptr支持数组操作
unique_ptr<int[]> uArrp(new int[3]{1,2,3});
// 重载了[]
cout << uArrp[0] << uArrp[1] << endl;
// 自定义删除器,保存的是int*
std::unique_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
// 默认调用deletep[]
std::unique_ptr<int[]> up(new int[10]);
// 初始化为0
std::unique_ptr<int[]> up(new int[10]());
备注:
unique_ptr
构造函数是explicit
的吗? 是的
explicit unique_ptr(pointer __p) noexcept : _M_t(__p) { }
unique_ptr
多态
- 基类智能指针保存子类对象,释放内存的时候需要virtual基类的析构
class Base {
public:
Base(int age, string name) : age_(age), name_(name) {
cout << "Base 构造" << age_ << name_ << endl;
};
virtual ~Base() {
cout << "Base 析构" << age_ << name_ << endl;
}
public:
int age_;
string name_;
};
class Son : public Base {
public:
Son(int age, string name) : Base(age, name) {
cout << "Son 构造" << age_ << name_ << endl;
};
~Son() {
cout << "Son 析构" << age_ << name_ << endl;
}
};
unique_ptr<Base> up(new Son(223, "zz")); // 只会调用父类的析构,需要重写子类析构才会发生多态