😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:2024-06-12 23:36:22
本文未经允许,不得转发!!!
目录
- 🎄一、概述
- ✨1.1 移动语义
- ✨1.2 右值引用
- 🎄二、移动构造函数(move constructor)
- 🎄三、使用移动构造函数
- 🎄四、总结
🎄一、概述
移动构造函数、移动赋值函数都是C++11增加的,了解这两个函数之前需要先弄清楚 移动语义 和 右值引用。
✨1.1 移动语义
移动语义是C++11才实现的,它的意义是对于一些即将被销毁的对象,可以不复制它,而是窃取它的资源,以此来减少一次复制,达到大幅度提升性能的效果。
在C++11标准之前,没有直接的方法移动对象,即使某些情况不需要拷贝对象,也不得不拷贝。例如,函数返回非引用的返回值时,会先构造一个临时对象作为返回值,函数调用时将该临时对象赋值给接收对象,调用结束后会销毁该临时对象。这里的临时对象是肯定会被销毁的,如果能够将临时对象的资源直接移交给接收对象,那么就可以减少一次拷贝。旧标准则只能拷贝,然后再销毁临时对象的资源。
C++11支持将即将被销毁的对象移动到接收对象,而不是复制。如下图,临时对象的资源被移动到接收对象,减少一次复制。
下面代码演示了复制的过程,等学习了下面的移动构造函数就可以编写移动的代码了。
// g++ 14_Copy_Date.cpp -std=c++11
#include <iostream>
#include <stdio.h>
#include <string.h>using namespace std;#define MAX_NEW_MEM (64*1000*1000)class CDate
{
public:CDate(int year, int mon, int day); // 构造函数声明CDate(const CDate& date); // 拷贝构造函数声明~CDate(); // 析构函数声明CDate operator+(int day); // 加号运算符声明void show(){cout << "Date: " << m_year << "." << m_mon << "." << m_day << ", this=" << this << endl;//cout << "Date: " << str << endl;}private:int m_year;int m_mon;int m_day;char *str;
};// 构造函数定义
CDate::CDate(int year, int mon, int day)
{m_year = year;m_mon = mon;m_day = day;str = new char[MAX_NEW_MEM];sprintf(str, "%4d.%02d.%02d", year,mon,day);cout << "Calling Constructor" << ", this=" << this <<endl;
}// 拷贝构造函数定义
CDate::CDate(const CDate& date)
{m_year = date.m_year;m_mon = date.m_mon;m_day = date.m_day;str = new char[MAX_NEW_MEM];memcpy(str, date.str, MAX_NEW_MEM);cout << "Calling Copy Constructor" << ", this=" << this << ", Copy Data" <<endl;
}// 析构函数定义
CDate::~CDate()
{cout << "Calling Destructor" << ", this=" << this <<endl;delete [] str;
}CDate CDate::operator+(int day)
{CDate temp = *this;temp.m_day += day;cout << "Calling operator+" << ", this=" << &temp << endl;return temp;
}int main()
{CDate date(2024,06,07);cout << endl;CDate date1 = std::move(date+1);// std::move 强制将 date+1 的求值结果转为右值date1.show();cout << endl;return 0;
}
运行结果如下:
1、std::move 强制将 date+1 的求值结果转为右值,避免编译器优化;
2、temp 对象赋值给date1之后,就销毁了,如果可以直接将temp对象的资源给到date1,就可以减少一次复制。
✨1.2 右值引用
前面介绍了C++11的移动语义,那这个移动语义是怎样实现的呢?
C++11标准引入的一种新的引用类型——右值引用,来实现移动操作。右值引用只能关联到右值,而右值要么是字面值,要么是求值过程中产生的临时对象。右值引用的定义使用了&&
,可以参考下面代码:
int &&rri = 13; // 定义了右值引用 rri
上个小节的临时对象只会在程序中很短暂的停留,使用完之后会被立即销毁。C++11标准通过 右值引用 来完全地接管这些临时对象,然后将临时对象的资源 移动 到接收对象,并且要保证这个临时对象可以被正常销毁。这就是C++11标准实现移动语义的原理。
关于左值、右值、右值引用还有疑惑的,可以参考这篇文章:【C++ | 左值、右值】一文了解C++的左值、右值、左值引用(&)、右值引用(&&)
🎄二、移动构造函数(move constructor)
移动构造函数(move constructor),是C++11标准增加的在创建对象时移动旧对象资源的构造函数。
类的移动构造函数原型通常是这样的:类名(类类型 &&);
。以CDate
类为例,其移动构造函数声明如下:
CDate(CDate &&date);
移动构造函数的几个特点:
1、函数名和类名相同,没有返回值,因为它也是构造函数的一种;
2、第一个参数必须是一个自身类类型的右值引用(&&),且其他参数都有默认值。
3、第一个参数不能声明为 const 右值引用的原因是该引用在函数内会被修改(移动资源)。
4、移动构造函数执行后,需要保证右值引用的对象能够被正常销毁。
下面看看怎样声明定义自己的移动构造函数:
CDate(CDate &&date) noexcept; // 声明
CDate::CDate(CDate&& date) noexcept // 实现
{m_year = date.m_year;m_mon = date.m_mon;m_day = date.m_day;str = date.str;date.str = NULL;cout << "Calling Move Constructor" << ", this=" << this <<endl;
}
移动构造函数解析:
- 1、处理普通数据成员,直接使用旧对象的进行赋值;
- 2、处理指向堆内存的指针,直接堆内存地址给新对象的指针,旧对象指针指向NULL。
语句str = date.str;
将旧对象的str直接给到正在创建的对象,而不重新new、复制。
语句date.str = NULL;
将旧对象的str赋值为NULL,保证其可以被正常销毁,delete NULL;
、delete[] NULL
不会造成任何问题。 - 3、由于移动操作“ 窃取” 资源, 它通常不分配任何资源。 因此, 移动操作通常不会抛出任何异常。不抛出异常的函数应该使用
noexcept
通知标准库,避免编译器为了处理异常而作一些额外的工作。
🎄三、使用移动构造函数
1、使用移动构造函数就需要弄清楚移动构造函数是什么时候会被调用的?
如果使用一个右值(即将被销毁的对象)去初始化同类型的对象,就会调用该类的移动构造函数。
2、怎样的类需要定义移动构造函数?
如果该类的成员存在指针指向new分配的堆内存,则可以跟进需要定义移动构造函数。
3、什么时候编译器会提供默认移动构造函数?
如果该类没有定义拷贝构造函数,且没有定义移动构造函数,则编译器会提供一个 默认的移动构造函数。
如果该类定义了拷贝构造函数,则编译器不会提供 默认的移动构造函数,需要用到移动构造的地方都会调用拷贝构造函数。
如果该类定义了移动构造函数,则编译器不会提供 默认的拷贝构造函数,需要用到拷贝构造的时候,编译器会报错。
4、默认的移动构造函数做了什么工作?
默认的移动构造函数 所做的工作与默认拷贝构造函数的工作一样,执行逐成员初始化并复制内置类型。如果成员是类对象,将使用相应类的构造函数和赋值运算符,就像参数为右值一样。
下面是第一小节的代码,加了移动构造函数后的例子:
// g++ 14_Move_Constructor_Date.cpp -std=c++11
#include <iostream>
#include <stdio.h>
#include <string.h>using namespace std;#define MAX_NEW_MEM (64*1000*1000)class CDate
{
public:CDate(int year, int mon, int day); // 构造函数声明CDate(const CDate& date); // 拷贝构造函数声明CDate(CDate&& date) noexcept; // 移动构造函数声明~CDate(); // 析构函数声明CDate operator+(int day); // 加号运算符声明void show(){cout << "Date: " << m_year << "." << m_mon << "." << m_day << ", this=" << this << endl;//cout << "Date: " << str << endl;}private:int m_year;int m_mon;int m_day;char *str;
};// 构造函数定义
CDate::CDate(int year, int mon, int day)
{m_year = year;m_mon = mon;m_day = day;str = new char[MAX_NEW_MEM];sprintf(str, "%4d.%02d.%02d", year,mon,day);cout << "Calling Constructor" << ", this=" << this <<endl;
}// 拷贝构造函数定义
CDate::CDate(const CDate& date)
{m_year = date.m_year;m_mon = date.m_mon;m_day = date.m_day;str = new char[MAX_NEW_MEM];memcpy(str, date.str, MAX_NEW_MEM);cout << "Calling Copy Constructor" << ", this=" << this << ", Copy Data" <<endl;
}CDate::CDate(CDate&& date) noexcept
{m_year = date.m_year;m_mon = date.m_mon;m_day = date.m_day;str = date.str;date.str = NULL;cout << "Calling Move Constructor" << ", this=" << this <<endl;
}// 析构函数定义
CDate::~CDate()
{cout << "Calling Destructor" << ", this=" << this <<endl;delete [] str;
}CDate CDate::operator+(int day)
{CDate temp = *this;temp.m_day += day;cout << "Calling operator+" << ", this=" << &temp << endl;return temp;
}int main()
{CDate date(2024,06,07);cout << endl;CDate date1 = std::move(date+1);// std::move 强制将 date+1 的求值结果转为右值date1.show();cout << endl;return 0;
}
运行结果:可以看到相比于第一小节的代码,这里调用了移动构造函数,减少了一次拷贝。
🎄四、总结
👉本文介绍C++11的移动构造函数,讲解了为什么需要移动构造函数,怎样声明、定义移动构造函数,怎样使用移动构造函数。
移动构造函数的目的就是为了减少一次拷贝,提升性能。C++11主要通过右值引用来实现移动语义,在使用右值(即将销毁的对象)去创建对象时,调用移动构造函数而不调用拷贝构造函数,以此减少一次拷贝。
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁