当给函数传递对象当做函数参数时,可以使用引用类型来减少拷贝对象的代价,尤其是避免容器的拷贝等。?但是当把函数内的局部对象当做返回值时,我们无法返回该局部对象的引用,导致每次返回局部对象都会进行拷贝。?因为返回局部对象的引用是无意义的,当函数调用完成,局部对象就被析构,所以其引用指向了一块析构的内存。程序如果使用移动操作,避免了拷贝,将新变量指向了局部变量的内容。例如:
std::vector<int> GetNums() {
? std::vector<int> nums;
? nums.push_back(1);
? nums.push_back(2);
? return nums;
}
std::vector<int> result = GetNums(); ?// 调用vector的移动构造函数,避免了拷贝。 标准库的容器vector,string等实现了拷贝语义,所以这些容器作为函数的局部对象时都可以直接返回。?
C++11提供了移动语义,分别是移动构造函数和移动赋值运算符,那么什么时候会触发移动语义?
当右值引用被当做初始值或者赋值操作的右侧运算对象时,程序将使用移动操作。
1. 对于移动构造函数两种情况会触发移动语义:函数返回把临时对象当做返回值,使用std::move。
2. 对于移动赋值运算符三种情况会触发移动语义:函数返回把临时对象当做返回值,使用std::move, 临时对象。
以以下代码为例:
#include <string.h>
#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <utility>
#include <functional>
#include <algorithm>
#include <cassert>
class Student {
public:
Student() {
id_ = 0;
name_ = nullptr;
}
Student(int id, const char* name) {
std::cout << "Constructor" << std::endl;
id_ = id;
size_t len = strlen(name);
name_ = new char[len + 1];
strcpy(name_, name);
}
~Student() {
std::cout << "De-constructor" << std::endl;
id_ = 0;
if (name_ != nullptr) {
delete[] name_;
name_ = nullptr;
}
}
Student(const Student& student) {
std::cout << "Copy Constructor" << std::endl;
id_ = student.id_;
size_t len = strlen(student.name_);
name_ = new char[len + 1];
strcpy(name_, student.name_);
}
Student(Student&& student) {
std::cout << "Move Constructor" << std::endl;
id_ = student.id_;
name_ = student.name_;
student.id_ = 0;
student.name_ = nullptr;
}
Student& operator=(const Student& student) {
std::cout << "Copy Assignment" << std::endl;
if (this == &student) {
return *this;
}
id_ = student.id_;
size_t len = strlen(student.name_);
name_ = new char[len + 1];
strcpy(name_, student.name_);
return *this;
}
Student& operator=(Student&& student) {
std::cout << "Move Assignment" << std::endl;
if (this == &student) {
return *this;
}
if (name_ != nullptr) {
delete[] name_;
}
id_ = student.id_;
name_ = student.name_;
student.id_ = 0;
student.name_ = nullptr;
return *this;
}
void Print() {
std::cout << id_ << " : " << (name_ == nullptr ? "NULL": name_) << std::endl;
}
private:
int id_;
char* name_;
};
void PrintStudent(Student student) {
student.Print();
}
Student GetStudent() {
printf("get student\r\n");
Student student(123, "123");
return student;
}
int main() {
Student a(1, "1");
std::cout << "====================================" << std::endl;
PrintStudent(a);
//copy constructor : 按值传递形参,需要将a拷贝到一个临时对象‘tempA’
//de-con :tempA析构
std::cout << "================1====================" << std::endl;
PrintStudent(Student(2, "2"));
//con:(2,'2')构建一个临时对象作为形参传递给PrintStudent(引用方式)
//de-con:临时对象析构
std::cout << "================2====================" << std::endl;
PrintStudent(std::move(Student(2, "2")));
//con: (2,"2")构建一个临时对象A(左值)
//move con:将临时对象A move成右值引用对象B
//de-con: 临时对象A析构
//de-con:临时对象B析构
std::cout << "================3====================" << std::endl;
PrintStudent(std::move(a));
a.Print();
//move con:将对象a move成右值引用对象b
//de-con: 对象b析构
std::cout << "================4====================" << std::endl;
Student a2 = GetStudent();
//con:当函数返回值为对象时,返回临时对象的引用,相当于直接构造新对象避免了拷贝
std::cout << "================5====================" << std::endl;
Student a3;
a3 = Student(3, "3");
//con:构造临时对象
//move assign:将临时对象copy给a3, 此时用的是move assign
//de-con: 临时对象析构
std::cout << "================6====================" << std::endl;
Student a4(4, "4");
a3 = a4;
//con:构造对象a4
//copy assign: 对象a4赋值给a3, 此时用的是copy assign
std::cout << "================7====================" << std::endl;
a3 = std::move(a4);
//move assign : 将对象a4 move成右值引用对象3,此时用的是move assign
std::cout << "================8====================" << std::endl;
a3 = GetStudent();
//con : 构造对象student
//move assign: student对象赋值给a3, 此时用的是move assign
//de-con: student对象析构
std::cout << "================9====================" << std::endl;
return 0;
}
参考文章:
C++11右值引用(一看即懂)??????c++ 之 std::move 原理实现与用法总结_学之知之的博客-CSDN博客_std::move
C++11的移动语义_博客-CSDN博客
|