C++学习之移动语义与智能指针详解

移动语义

1.几个基本概念的理解

(1)可以取地址的是左值,不能取地址的就是右值,右值可能存在寄存器,也可能存在于栈上(短暂存在栈)上

(2)右值包括:临时对象、匿名对象、字面值常量

(3)const 左值引用可以绑定到左值与右值上面,称为万能引用。正因如此,也就无法区分传进来的参数是左值还是右值。

const int &ref = a;//const左值引用可以绑定到左值
const int &ref1 = 10;//const左值引用可以绑定到右值

(4)右值引用:只能绑定到右值不能绑定到左值

2.移动构造函数

注意:

移动函数(移动构造函数和移动赋值运算符函数)优先于复制函数(拷贝构造函数和赋值运算符函数)的执行;具有移动语义的函数(移动构造函数和移动赋值运算符函数)优先于具有复制控制语义函数(拷贝构造函数和赋值运算符函数)的执行

String(String &&rhs)
: _pstr(rhs._pstr)
{
cout << "String(String &&)" << endl;
rhs._pstr = nullptr;
}

3.移动赋值函数

String &operator=(String &&rhs)
{
    cout << "String &operator=(String &&)" << endl;
    if(this != &rhs)//1、自移动
    {
        delete [] _pstr;//2、释放左操作数
        _pstr = nullptr;
        _pstr = rhs._pstr;//3、浅拷贝
        rhs._pstr = nullptr;
    }

    return *this;//4、返回*this
}

4.std::move函数

std::move:

原理:将左值转换为右值,在内部其实上是做了一个强制转换,static_cast<T &&>(lvaule)。将左值转换为右值后,左值就不能直接使用了,如果还想继续使用,必须重新赋值。std::move()作用于内置类型没有任何作用,内置类型本身是左值还是右值,经过std::move()后不会改变。

5.面试题,关于实现String

#include <iostream>
#include <string>

using std::string;
using std::cout;
using std::endl;

class String
{
public:
    //(当传递右值的时候)具有移动语义的函数优先于具有复制控制语义的函数
    //移动构造函数(只针对右值)
    String(String &&rhs)
    : _pstr(rhs._pstr)
    {
        cout << "String(String &&)" << endl;
        rhs._pstr = nullptr;
    }

    //移动赋值运算符函数(传入右值)
    String &operator=(String &&rhs)
    {
        cout << "String &operator=(String &&)" << endl;
        if(this!=&rhs){     //不能自复制
            delete [] _pstr;//释放左操作数
            _pstr = nullptr;

            _pstr=rhs._pstr;//转移右操作的资源
            rhs._pstr=nullptr;//释放右值
        }
        return *this;
    }

private:
    char* _pstr;
};

资源管理和智能指针

一、C语言中的问题

C语言在对资源管理的时候,比如文件指针,由于分支较多,或者由于写代码的人与维护的人不一致,导致分支没有写的那么完善,从而导致文件指针没有释放,所以可以使用C++的方式管理文件指针。。。

class SafeFile
{
public:
    //在构造的时候托管资源(fp)
    SafeFile(FILE *fp)
    : _fp(fp)
    {
        cout << "SafeFile(FILE *)" << endl;
        if(nullptr==fp)
        {
            cout << "nullptr == _fp " << endl;
        }
    }

    //提供若干访问资源的方法
    void write(const string &msg)
    {
        fwrite(msg.c_str(),1,msg.size(),_fp);  //调用c语言的函数往_fp输入数据
    }

    //在销毁(析构)时候释放资源(fp)
    ~SafeFile()
    {
        cout << "~SafeFile()" << endl;
        if(_fp)
        {
            fclose(_fp);
            cout << "fclose(_fp)" << endl;
        }
    }
private:
    FILE *_fp;
};
void test()
{
    string s1 = "hello,world\n";
    SafeFile sf(fopen("text.txt","a+"));
    sf.write(s1);
}

二、C++的解决办法(RAII技术)

1)概念:资源管理 RAII 技术,利用对象的生命周期管理程序资源(包括内存、文件句柄、锁等)的技术,因为对象在离开作用域的时候,会自动调用析构函数

2)关键:要保证资源的释放顺序与获取顺序严格相反。。正好是析构函数与构造函数的作用

3)RAII常见特征

1、在构造时初始化资源,或者托管资源。
2、析构时释放资源。
3、一般不允许复制或者赋值(值语义-对象语义)
4、提供若干访问资源的方法。

4)区分:值语义:可以进行复制与赋值。

5)对象语义:不能进行复制与赋值,一般使用两种方法达到要求:

(1)、将拷贝构造函数和赋值运算符函数设置为私有的就 ok 。

(2)、将拷贝构造函数和赋值运算符函数使用=delete.

6)RAII技术代码

template <typename T>
class RAII
{
public:
    //通过构造函数托管资源
    RAII(T *data)
    : _data(data)
    {
        std::cout<< "RAII(T *)" << std::endl;
    }

    //访问资源的方法
    T *operator->()
    {
        return _data;
    }
    T &operator*()
    {
        return *_data;
    }
    T *get()const
    {
        return _data;
    }
    void reset(T *data)
    {
        if(_data)
        {
            delete _data;
            _data = nullptr;
        }
        _data = data;
    }

    //不能赋值和复制
    RAII(const RAII&rhs) = delete;
    RAII&operator=(const RAII&rhs)=delete;

    //通过析构函数释放资源
    ~RAII()
    {
        cout << "~RAII()" << endl;
        if(_data)
        {
            delete _data;
            _data = nullptr;
        }
    }

private:
    T *_data;
};
void test3()
{
    //这里没给出Point类的实现方式
    RAII<Point> ppt(new Point(1,2));
    cout<<"ppt = ";
    ppt->print();
    cout<<endl;
}

三、四种智能指针

RAII的对象ppt就有智能指针的雏形。

1、auto_ptr.cc

最简单的智能指针,使用上存在缺陷,所以被弃用。。。(C++17已经将其删除了)

2、unique_ptr

比auto_ptr安全多了,明确表明是独享所有权的智能指针,所以不能进行复制与赋值。

    unique_ptr<int> up(new int(10));
    cout<<"*up="<<*up<<endl;              //打印10
    cout<<"up.get() = "<<up.get()<<endl;  //获取托管的指针的值,也就是10的地址

    cout << endl << endl;
    /* unique_ptr<int> up2(up);//error,独享资源的所有权,不能进行复制 */

    unique_ptr<int> up4(std::move(up));  //通过移动语义转移up的所有权
    cout<<"*up="<<*up4<<endl;
    cout<<"up.get() = "<<up4.get()<<endl;

    unique_ptr<Point> up5(new Point(3,4));//通过移动语义转移up的所有权
    vector<unique_ptr<Point>> numbers;
    numbers.push_back(unique_ptr<Point>(new Point(1,2)));
    numbers.push_back(std::move(up5));

3、shared_ptr

    shared_ptr<int> sp(new int(10));
    cout << "*sp = " << *sp << endl;        //打印10
    cout << "sp.get() = " << sp.get() << endl;  //地址
    cout << "sp.use_count() = " << sp.use_count() << endl;  //引用次数为1

    cout<<endl<<endl;
    //提前结束栈对象
    {
        shared_ptr<int> sp2(sp);//共享所有权,使用浅拷贝
        cout << "*sp = " << *sp << endl;
        cout << "sp.get() = " << sp.get() << endl;
        cout << "sp.use_count() = " << sp.use_count() << endl;
        cout << "*sp2 = " << *sp2 << endl;
        cout << "sp2.get() = " << sp2.get() << endl;              //地址都一样
        cout << "sp2.use_count() = " << sp2.use_count() << endl;  //引用次数加1,变为2了
    }

    cout << "sp.use_count() = " << sp.use_count() << endl;  //又变为1了

    cout << endl << endl;
    shared_ptr<Point> sp4(new Point(3.4));//通过移动语义转移sp的所有权
    vector<shared_ptr<Point>> numbers;
    numbers.push_back(shared_ptr<Point> (new Point(1,2)));
    numbers.push_back(sp4);
    numbers[0]->print();
    numbers[1]->print();

3.1、循环引用

该智能指针在使用的时候,会使得引用计数增加,从而会出现循环引用的问题,两个shared_ptr智能指针互指,导致引用计数增加,不能靠对象的销毁使得引用计数变为0,从而导致内存泄漏。。

class Child;
class Parent
{
public:
  Parent()
 {
    cout << "Parent()" << endl;
 }
  ~Parent()
 {
    cout << "~Parent()" << endl;
 }
  shared_ptr<Child> pParent;
};
class Child
{
public:
  Child()
 {
    cout << "Child()" << endl;
 }
  ~Child()
 {
    cout << "~Child()" << endl;
 }
  shared_ptr<Parent> pChild;
};
void test()
{
  //循环引用可能导致内存泄漏
  shared_ptr<Parent> parentPtr(new Parent());
  shared_ptr<Child> childPtr(new Child());
  cout << "parentPtr.use_count() = " << parentPtr.use_count() << endl;
  cout << "childPtr.use_count() = " << childPtr.use_count() << endl;

  cout << endl << endl;
  parentPtr->pParent = childPtr;//sp = sp
  childPtr->pChild = parentPtr;
  cout << "parentPtr.use_count() = " << parentPtr.use_count() << endl;
  cout << "childPtr.use_count() = " << childPtr.use_count() << endl;
}

1.解决循环引用的办法是使得其中一个改为weak_ptr,不会增加引用计数,这样可以使用对象的销毁而打破引用计数减为0的问题。。

2.修改: shared_ptr pChild;改为 weak_ptr pChild;即可解决循环引用的问题。。

parentPtr->pParent = childPtr;//sp = sp
childPtr->pChild = parentPtr;//wp = sp,weak_ptr不会导致引用计数加1

4、weak_ptr

与shared_ptr相比,称为弱引用的智能指针,shared_ptr是强引用的智能指针。weak_ptr不会导致引用计数增加,但是它不能直接获取资源,必须通过lock函数从wp提升为sp,从而判断共享的资源是否已经销毁

    weak_ptr<Point> wp
    {
        shared_ptr<Point> sp(new Point(1,2));
        wp = sp;
        cout << "wp.use_count = " << wp.use_count() << endl;
        cout << "sp.use_count = " << sp.use_count() << endl;

        cout<<"wp.expired = "<<wp.expired()<<endl;//此方法等同于use_count()==0?
        //等于0表示false,空间还存在
        //不等0表示true,空间已经不存在了
        //expired = use_count
        shared_ptr<Point> sp2 = wp.lock();//判断共享的资源是否已经销毁的方式就是从wp提升为sp
        if(sp2)
        {
            cout << "提升成功" << endl;
        }
        else
        {
            cout << "提升失败" << endl;
        }
     }

四、为智能指针定制删除器

1)很多时候我们都用new来申请空间,用delete来释放。库中实现的各种智能指针,默认也都是用delete来释放空间,但是若我们采用malloc申请的空间或是用fopen打开的文件,这时我们的智能指针就无法来处理,因此我们需要为智能指针定制删除器,提供一个可以自由选择析构的接口,这样,我们的智能指针就可以处理不同形式开辟的空间以及可以管理文件指针。

2)自定义智能指针的方式有两种:

(1)函数指针

(2)仿函数(函数对象)

函数指针的形式:

template<class T>
void Free(T* p)
{
  if (p)
    free(p);
}
template<class T>
void Del(T* p)
{
  if (p)
    delete p;
}
void FClose(FILE* pf)
{
  if (pf)
    fclose(pf);
}
//定义函数指针的类型
typedef void(*DP)(void*);
template<class T>
class SharedPtr
{
public:
  SharedPtr(T* ptr = NULL ,DP dp=Del)
 :_ptr(ptr)
 , _pCount(NULL)
 , _dp(dp)
 {
    if (_ptr != NULL)
   {
      _pCount = new int(1);
   }
 }
private:
  void Release()
 {
    if (_ptr&&0==--GetRef())
   {
      //delete _ptr;
      _dp(_ptr);
      delete _pCount;
   }
 }
  int& GetRef()
  {
    return *_pCount;
 }
private:
  T* _ptr;
  int* _pCount;
  DP _dp;
};

仿函数(函数对象)

关于删除器的使用

五、智能指针的误用

1、同一个裸指针被不同的智能指针托管,导致被析构两次。

1.1、直接使用

1.2、间接使用

2、还是裸指针被智能指针托管形式,但是比较隐蔽。。。

class Point
: public std::enable_shared_from_this<Point>
{
public:
  Point(int ix = 0, int iy = 0)
 : _ix(ix)
 , _iy(iy)
 {
    cout << "Point(int = 0, int = 0)" << endl;
 }
  void print() const
 {
    cout << "(" <<_ix
       << ","  << _iy
       << ")" << endl;
 }
  /* Point *addPoint(Point *pt) */
  shared_ptr<Point> addPoint(Point *pt)
 {
    _ix += pt->_ix;
    _iy += pt->_iy;
    //this指针是一个裸指针
    /* return shared_ptr<Point>(this); */
    return shared_from_this();
 }
  ~Point()
 {
    cout << "~Point()" << endl;
 }
private:
  int _ix;
  int _iy;
};
void test3()
{
  shared_ptr<Point> sp1(new Point(1, 2));
  cout << "sp1 = ";
  sp1->print();
  cout << endl;
  shared_ptr<Point> sp2(new Point(3, 4));
  cout << "sp2 = ";
  sp2->print();
  cout << endl;
  shared_ptr<Point> sp3(sp1->addPoint(sp2.get()));
  cout << "sp3 = ";
  sp3->print();
}

总结

到此这篇关于C++学习之移动语义与智能指针的文章就介绍到这了,更多相关C++移动语义与智能指针内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++智能指针实例详解

    本文通过实例详细阐述了C++关于智能指针的概念及用法,有助于读者加深对智能指针的理解.详情如下: 一.简介 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete.程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见. 用智能指针便可以有效缓解这类问题,本文主要讲解参见的智能指针的用法.包括:std::auto_ptr.boost::scoped_ptr.boost::shared_p

  • C++11新特性之智能指针(shared_ptr/unique_ptr/weak_ptr)

    shared_ptr基本用法 shared_ptr采用引用计数的方式管理所指向的对象.当有一个新的shared_ptr指向同一个对象时(复制shared_ptr等),引用计数加1.当shared_ptr离开作用域时,引用计数减1.当引用计数为0时,释放所管理的内存. 这样做的好处在于解放了程序员手动释放内存的压力.之前,为了处理程序中的异常情况,往往需要将指针手动封装到类中,通过析构函数来释放动态分配的内存:现在这一过程就可以交给shared_ptr去做了. 一般我们使用make_shared来

  • C++ 智能指针的模拟实现实例

    C++ 智能指针的模拟实现实例 1.引入 int main() { int *p = new int; //裸指针 delete p; return 0; } 在上面的代码中定义了一个裸指针p,需要我们手动释放.如果我们一不小心忘记释放这个指针或者在释放这个指针之前,发生一些异常,会造成严重的后果(内存泄露).而智能指针也致力于解决这种问题,使程序员专注于指针的使用而把内存管理交给智能指针. 普通指针也容易出现指针悬挂问题,当有多个指针指向同一个对象的时候,如果某一个指针delete了这个对象,

  • C++智能指针读书笔记

    最近在补看<C++ Primer Plus>第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑.C++面试过程中,很多面试官都喜欢问智能指针相关的问题,比如你知道哪些智能指针?shared_ptr的设计原理是什么?如果让你自己设计一个智能指针,你如何完成?等等--.而且在看开源的C++项目时,也能随处看到智能指针的影子.这说明智能指针不仅是面试官爱问的题材,更是非常有实用价值. C++通过一对运算符 new 和 delete 进行动态内存管理,new在动态内存中

  • C++11中value category(值类别)及move semantics(移动语义)的介绍

    前言 C++11之前value categories只有两类,lvalue和rvalue,在C++11之后出现了新的value categories,即prvalue, glvalue, xvalue.不理解value categories可能会让我们遇到一些坑时不知怎么去修改,所以理解value categories对于写C++的人来说是比较重要的.而理解value categories离不开一个概念--move semantics.了解C++11的人我相信都了解了std::move,右值引用

  • C++ 智能指针深入解析

    1. 为什么需要智能指针?简单的说,智能指针是为了实现类似于Java中的垃圾回收机制.Java的垃圾回收机制使程序员从繁杂的内存管理任务中彻底的解脱出来,在申请使用一块内存区域之后,无需去关注应该何时何地释放内存,Java将会自动帮助回收.但是出于效率和其他原因(可能C++设计者不屑于这种傻瓜氏的编程方式),C++本身并没有这样的功能,其繁杂且易出错的内存管理也一直为广大程序员所诟病. 更进一步地说,智能指针的出现是为了满足管理类中指针成员的需要.包含指针成员的类需要特别注意复制控制和赋值操作,

  • C++中智能指针如何设计和使用

    智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露.它的一种通用实现技术是使用引用计数(reference count).智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针.每次创建类的新对象时,初始化指针并将引用计数置为1:当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数:对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果

  • 详解C++11中的右值引用与移动语义

    C++11的一个最主要的特性就是可以移动而非拷贝对象的能力.很多情况都会发生对象的拷贝,有时对象拷贝后就立即销毁,在这些情况下,移动而非拷贝对象会大幅度提升性能. 右值与右值引用 为了支持移动操作,新标准引入了一种新的引用类型--右值引用,就是必须绑定到右值的引用.我们通过&&而不是&来获得右值引用.右值引用一个重要的特性就是只能绑定到将要销毁的对象. 左值和右值是表达式的属性,一些表达式生成或要求左值,而另一些则生成或要求右值.一般而言,一个左值表达式表示的是一个对象的身份,而右

  • 深入了解c++11 移动语义与右值引用

    1.移动语义 C++11新标准中一个最主要的特性就是提供了移动而非拷贝对象的能力.如此做的好处就是,在某些情况下,对象拷贝后就立即被销毁了,此时如果移动而非拷贝对象会大幅提升性能.参考如下程序: //moveobj.cpp #include <iostream> #include <vector> using namespace std; class Obj { public: Obj(){cout <<"create obj" << e

  • C++学习之移动语义与智能指针详解

    移动语义 1.几个基本概念的理解 (1)可以取地址的是左值,不能取地址的就是右值,右值可能存在寄存器,也可能存在于栈上(短暂存在栈)上 (2)右值包括:临时对象.匿名对象.字面值常量 (3)const 左值引用可以绑定到左值与右值上面,称为万能引用.正因如此,也就无法区分传进来的参数是左值还是右值. const int &ref = a;//const左值引用可以绑定到左值 const int &ref1 = 10;//const左值引用可以绑定到右值 (4)右值引用:只能绑定到右值不能绑

  • C++智能指针详解

    目录 一. unique_ptr独占指针 特点 创建方式 传递方式 简单使用 隐藏危险 二. shared_ptr 计数指针 特点 传递方式 隐藏危险 三. weak_ptr 优缺点: 智能指针由原始指针的封装,优点是可以自动分配内存,不用担心内存泄漏问题. 用于解决独占/共享所有权指针的释放,传输等问题. 但是没有原始指针方便. 一. unique_ptr独占指针 特点 都是围绕独占展开 特点一: 如其名,独占.也就是说同一个内存空间同时只能有一个指针来管理. int* pi = new in

  • C++ boost scoped_ptr智能指针详解

    目录 一.智能指针-唯一所有者 二.接口类分析 一.智能指针-唯一所有者 boost::scoped_ptr 是一个智能指针,它是动态分配对象的唯一所有者. boost::scoped_ptr 无法复制或移动.此智能指针在头文件 boost/scoped_ptr.hpp 中定义. 二.接口类分析 scoped_array 分析 scoped_array 的类部分原始代码如下: template<class T> class scoped_array // noncopyable { priva

  • C++ Boost PointerContainer智能指针详解

    目录 一.提要 二.智能指针Boost.PointerContainer 三.练习 一.提要 在 C++11 中,Boost.PointerContainer是另一个智能指针,一般是用来生成集合数据的,本文阐述这种指针的特点和用法. 二.智能指针Boost.PointerContainer 库 Boost.PointerContainer 提供专门用于管理动态分配对象的容器.例如,在 C++11 中,您可以使用 std::vector<std::unique_ptr<int>> 创

  • 一篇文章带你了解C++智能指针详解

    目录 为什么要有智能指针? 智能指针的使用及原理 RALL shared_ptr的使用注意事项 创建 多个 shared_ptr 不能拥有同一个对象 shared_ptr 的销毁 shared_ptr 的线程安全问题 shared_ptr 的循环引用 unique_ptr的使用 unique_ptr 总结 为什么要有智能指针? 因为普通的指针存在以下几个问题: 资源泄露 野指针 未初始化 多个指针指向同一块内存,某个指针将内存释放,别的指针不知道 异常安全问题 如果在 malloc和free 或

  • C++Smart Pointer 智能指针详解

    目录 一.为啥使用智能指针呢 二.shared_ptr智能指针 三.unique_ptr智能指针 四.weak_ptr智能指针 五.智能指针怎么解决交叉引用,造成的内存泄漏 5.1交叉引用的栗子: 5.2解决方案 六.智能指针的注意事项 总结 一.为啥使用智能指针呢 标准库中的智能指针: std::auto_ptr --single ownership (C++98中出现,缺陷较多,被摒弃) std::unique_ptr --single ownership (C++11替代std::auto

  • Go语言基础学习之指针详解

    目录 1. 什么是指针 2. 指针地址 & 指针类型 3. 指针取值 4. 空指针 5. make 6. new 7. make 和 new 的区别 8. 问题 今天来说说 Go 语言基础中的指针. Go 语言中指针是很容易学习的,Go 语言中使用指针可以更简单的执行一些任务. 1. 什么是指针 Go 语言中,一个指针变量指向了一个值的内存地址.和 C.C++ 中的指针不同,Go 语言中的指针不能进行计算和偏移操作. Go 语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一

  • C语言指针详解及用法示例

    新手在C语言的学习过程中遇到的最头疼的知识点应该就是指针了,指针在C语言中有非常大的用处.下面我就带着问题来写下我对于指针的一些理解. 指针是什么? 指针本身是一个变量,它存储的是数据在内存中的地址而不是数据本身的值.它的定义如下: int a=10,*p; p=&a int a=10; int *p=&a; 首先我们可以理解 int* 这个是要定义一个指针p,然后因为这个指针存储的是地址所以要对a取地址(&)将值赋给指针p,也就是说这个指针p指向a. 很多新手都会对这两种定义方法

  • C++ 中的this指针详解及实例

    C++ this 指针详解 学习 C++ 的指针既简单又有趣.通过指针,可以简化一些 C++ 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的.所以,想要成为一名优秀的 C++ 程序员,学习指针是很有必要的. 正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址. this指针是类的一个自动生成.自动隐蔽的私有成员,它存在于类的非静态成员中,指向被调用函数所在的对象. 全局仅有一个this指针,当一个对

  • Go语言学习之结构体和方法使用详解

    目录 1. 结构体别名定义 2. 工厂模式 3. Tag 原信息 4. 匿名字段 5. 方法 1. 结构体别名定义 变量别名定义 package main import "fmt" type integer int func main() { //类型别名定义 var i integer = 1000 fmt.Printf("值: %d, 类型: %T\n", i, i) var j int = 100 j = int(i) //j和i不属于同一类型,需要转换 fm

随机推荐