c++智能指针介绍之unique_ptr

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_ptr显示的将控制权转移(move)给另一个对象后将自己设置为nullptrauto_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]());

备注:

  1. unique_ptr构造函数是explicit的吗? 是的
explicit unique_ptr(pointer __p) noexcept : _M_t(__p) { }
  1. 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")); // 只会调用父类的析构,需要重写子类析构才会发生多态
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值