std::unique_ptr

std::unique_ptr是一种几乎和原始指针一样高效的智能指针,对所管理的指针资源拥有独占权。由C++11标准引入,用于替代C++98中过时的std::auto_ptr智能指针。相比而言,std::unique_ptr的优点有:

  • 语义更清晰:std::auto_ptr进行拷贝的时候实际执行的是移动语义,但C++98中并没有定义出移动语义,所以使用的时候可能会违背直觉。而std::unique_ptr利用了C++11中新定义的移动语义,只允许移动操作,禁止拷贝操作,从而让语义更加清晰。
  • 允许自定义删除器:由于std::unique_ptr将删除器作为自己的成员变量,所以传入自定义删除器之前需要在模板参数中指定删除器的类型std::unique_ptr<T, D> up(nullptr, deleter)。
  • 支持STL容器:在C++98中,容器要求元素必须是可以拷贝的,比如«effective STL»中提到的,对容器中的元素进行std::sort时,会从区间中选一个元素拷贝为主元素(pivot),然后再对所有元素进行分区操作。但是std::auto_ptr的拷贝操作执行的却是移动语义,这样就会造成bug。在C++11中,STL容器是支持移动语义的,std::unique_ptr只提供移动操作删除了拷贝操作,并且移动操作是noexcept的(这一点很重要,因为STL容器有些操作需要保证强异常安全会要求要么用拷贝操作要么用无异常的移动操作)。只要不涉及到拷贝的容器操作,比如fill函数,那么std::unique_ptr作为容器元素是正确的。

std::unique_ptr的大小

默认情况下,std::unique_ptr的大小和原始指针一样:

#include <iostream>
#include <memory> int main(int argc, const char* argv[]) {
std::unique_ptr<int> upNum(new int); // 输出8(64位操作系统)
std::cout << sizeof(upNum) << std::endl; return 0;
}

当添加删除器的时候,情况就发生了变化,std::unique_ptr的大小就等于原始指针的大小加上删除器类型的大小:

#include <iostream>
#include <memory> void deleter(int* pNum) {
std::cout << "function deleter" << std::endl;
delete pNum;
} int main(int argc, const char* argv[]) {
std::unique_ptr<int, decltype(&deleter)> upNum(new int, deleter); // 输出8+8=16(函数指针类型的大小也为8)
std::cout << sizeof(upNum) << std::endl; return 0;
}

这种情况是可以优化的,那就是使用仿函数或者lambda函数作为删除器,当仿函数或lambda函数是无状态(stateless)的时候,那么类类型作为类成员变量的时候,会自动优化掉空类所占用的空间:

#include <iostream>
#include <memory> int main(int argc, const char* argv[]) {
auto deleter = [](int* pNum) {
std::cout << "lambda deleter" << std::endl;
delete pNum;
};
std::unique_ptr<int, decltype(deleter)> upNum(new int, deleter); // 输出8
std::cout << sizeof(upNum) << std::endl; return 0;
}

使用场景1-工厂模式

#include <iostream>
#include <memory> class Foo {
public:
void greeting() noexcept {
std::cout << "hi! i am foo" << std::endl;
}
}; class Factory {
public:
std::unique_ptr<Foo> createFoo() {
return std::unique_ptr<Foo>(new Foo);
}
}; int main(int argc, const char* argv[]) {
auto foo = Factory().createFoo(); // 输出"hi! i am foo"
foo->greeting(); return 0;
}

使用场景2-实现pImpl模式

PIMPL(Pointer to Implementation)是通过将所有成员变量封装到私有的Impl结构体中,自身只保留一个指向结构体对象的一个私有的指针成员。

pImpl模式的优点有:1.降低模块的耦合度,2.提高编译速度,3.提高接口稳定性。

// Foo.h
#pragma once #include <memory>
#include <string> class Foo {
public:
Foo(); // 需要将~Foo的实现放入Foo.cpp中,避免出现delete imcomplete type错误
~Foo(); // 1.定义了~Foo之后不会自动生成移动函数
// 2.移动构造函数中因为会生成处理异常的代码,所以需要析构成员变量,也会造成delete imcomplete type问题,所以将实现放入Foo.cpp
// 3.移动赋值函数中因为会先删除自己指向的Impl对象指针,也会造成delete imcomplete type问题,所以将实现放入Foo.cpp
Foo(Foo&& rhs) noexcept;
Foo& operator=(Foo&& rhs) noexcept; // 由于unique_ptr不支持复制,所以无法生成默认拷贝函数
Foo(const Foo& rhs);
Foo& operator=(const Foo& rhs); void setName(std::string name);
const std::string& getName() const noexcept; private:
struct Impl;
std::unique_ptr<Impl> m_upImpl;
};
// Foo.cpp
#include "Foo.h"
#include <string> struct Foo::Impl {
std::string name;
}; Foo::Foo() : m_upImpl(new Impl) {} Foo::~Foo() = default; Foo::Foo(Foo&& rhs) noexcept = default;
Foo& Foo::operator=(Foo&& rhs) noexcept = default; Foo::Foo(const Foo& rhs) : m_upImpl(new Impl) {
*m_upImpl = *rhs.m_upImpl;
} Foo& Foo::operator=(const Foo& rhs) {
*m_upImpl = *rhs.m_upImpl;
return *this;
} void Foo::setName(std::string name) {
m_upImpl->name = name;
} const std::string& Foo::getName() const noexcept {
return m_upImpl->name;
}

尽量使用std::make_unique

使用std::make_unique来创建std::unique_ptr智能指针有以下优点:

  • 减少代码重复:从代码std::unique_ptr<Foo> upFoo(new Foo);和auto upFoo = std::make_unique<Foo>();可以得知使用make_unique只需要写一次Foo就可以,更加符合软件工程中的要求。

  • 提高异常安全性:当在函数调用中构造智能指针时,由于执行顺序的不确定性,有可能会造成资源泄露,比如对于代码:

    #include <iostream>
    #include <memory>
    #include <exception> bool priority() {
    throw std::exception(); return true;
    } void func(std::unique_ptr<int> upNum, bool flag) {
    if (flag) {
    std::cout << *upNum << std::endl;
    }
    } int main() {
    func(std::unique_ptr<int>(new int), priority()); return 0;
    }

    这里调用func函数时,会执行三个步骤

    1. new int
    2. std::unique_ptr<int>构造函数
    3. priority函数

    这里唯一可以确定的就是步骤1发生在步骤2之前,但步骤3的次序是不一定的,如果步骤3在步骤1和步骤2中间执行那么就会造成内存泄漏。但是如果使用make_unique就不会出现这个问题。

但是std::make_unique是C++14标准才引入的,所以使用C++11环境的话需要自己实现这个函数:

template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&&... params)
{
return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}
  • 素材来自于«effective modern c++»

智能指针(1)-std::unique_ptr的更多相关文章

  1. C++11智能指针之std::unique_ptr

    C++11智能指针之std::unique_ptr   uniqut_ptr是一种对资源具有排他性拥有权的智能指针,即一个对象资源只能同时被一个unique_ptr指向. 一.初始化方式 通过new云 ...

  2. c++——智能指针学习(unique_ptr)

    1.为什么会有unique_ptr? 动态内存忘记delete,导致内存泄漏.比如: p = new (); if(...) { return ; } delete p; 因此我们需要一种方式来解决这 ...

  3. 【校招面试 之 C/C++】第27题 C++ 智能指针(三)之 unique_ptr

    auto_ptr<string> p1(new string ("auto") : //#1 auto_ptr<string> p2; //#2 p2 = ...

  4. C++智能指针: auto_ptr, shared_ptr, unique_ptr, weak_ptr

    本文参考C++智能指针简单剖析 内存泄露 我们知道一个对象(变量)的生命周期结束的时候, 会自动释放掉其占用的内存(例如局部变量在包含它的第一个括号结束的时候自动释放掉内存) int main () ...

  5. C++11 新特性之智能指针(shared_ptr, unique_ptr, weak_ptr)

    这是C++11新特性介绍的第五部分,涉及到智能指针的相关内容(shared_ptr, unique_ptr, weak_ptr). shared_ptr shared_ptr 基本用法 shared_ ...

  6. C++ template的一些高级用法(元编码,可变参数,仿函数,using使用方法,. C++ 智能指针)

    1 .  通用函数可变参数模板 对于有些时候,我们无法确切的知道,函数的参数个数时,而又不想过多的使用所谓的函数重载,那么就可以效仿下面的例子: #include<iostream> #i ...

  7. c++智能指针(unique_ptr 、shared_ptr、weak_ptr、auto_ptr)

    一.前序 什么是智能指针? ——是一个类,用来存储指针(指向动态分配对象也就是堆中对象的的指针). c++的内存管理是让很多人头疼的事,当我们写一个new语句时,一般就会立即把delete语句直接也写 ...

  8. C++11 unique_ptr智能指针详解

    在<C++11 shared_ptr智能指针>的基础上,本节继续讲解 C++11 标准提供的另一种智能指针,即 unique_ptr 智能指针. 作为智能指针的一种,unique_ptr ...

  9. c++智能指针的使用,shared_ptr,unique_ptr,weak_ptr

    c++智能指针的使用 官方参考 普通指针的烦恼:内存泄漏,多次释放,提前释放 智能指针 负责自动释放所指向的对象. 三种智能指针 shared_ptr,unique_ptr,weak_ptr: 将sh ...

随机推荐

  1. Java 序列化界新贵 kryo 和熟悉的“老大哥”,就是 PowerJob 的序列化方案

    本文适合有 Java 基础知识的人群 作者:HelloGitHub-Salieri HelloGitHub 推出的<讲解开源项目>系列. 项目地址: https://github.com/ ...

  2. SpringBoot打Jar包在命令行运行

    首先写一个测试文件 然后点击IDEA右侧的maven,然后选择package,之后点击上面运行或者直接双击即可, 等下方控制台构建成功即可: 然后找到项目目录下target下即可看到打的jar包 然后 ...

  3. 6.AVCodecContext和AVCodec

    AVCodecContext  AVCodecContext 结构表示程序运行的当前 Codec 使用的上下文,着重于所有 Codec 共有的属性(并且是在程序运行时才能确定其值)和关联其他结构的字段 ...

  4. 《ASP.NET Core项目开发实战入门》带你走进ASP.NET Core开发

    <ASP.NET Core项目开发实战入门>从基础到实际项目开发部署带你走进ASP.NET Core开发. ASP.NET Core项目开发实战入门是基于ASP.NET Core 3.1 ...

  5. Kubernetes K8S之Pod跨namespace名称空间访问Service服务

    Kubernetes的两个Service(ServiceA.ServiceB)和对应的Pod(PodA.PodB)分别属于不同的namespace名称空间,现需要PodA和PodB跨namespace ...

  6. Go语言 | 并发设计中的同步锁与waitgroup用法

    今天是golang专题的第16篇文章,我们一起来聊聊golang当中的并发相关的一些使用. 虽然关于goroutine以及channel我们都已经介绍完了,但是关于并发的机制仍然没有介绍结束.只有go ...

  7. 喵的Unity游戏开发之路 - 多场景:场景加载

    如果丢失格式.图片或视频,请查看原文:https://mp.weixin.qq.com/s/RDVMg6l41uc2IHBsscc0cQ 很多童鞋没有系统的Unity3D游戏开发基础,也不知道从何开始 ...

  8. 通过调用标识符确定this

    一. 纲 this的性质 作用:表示函数执行时的环境 值:一个对象 特点:动态性 确定this的难度 JS语言的动态性: 函数的this在执行时才能确定 函数为一级公民 可作实参.返回值.数据赋值进行 ...

  9. Android端代码染色原理及技术实践

    导读 高德地图开放平台产品不断迭代,代码逻辑越来越复杂,现有的测试流程不能保证完全覆盖所有业务代码,测试不到的代码及分支,会存在一定的风险.为了保证测试全面覆盖,需要引入代码覆盖率做为测试指标,需要对 ...

  10. 【转】Locust性能-零基础入门系列(1)-wait_time属性用法

    本篇文章,从局部出发,利用一个简单的测试,来说明场景模拟的wait_time属性的用法.wait_time为什么要单独拎出来讲,是因为它主要有两种模式,而初学者对这两种模式,容易混淆.1) wait_ ...