C++11的移动语义
程序员文章站
2022-03-22 20:56:36
...
问题一:当给函数传递对象当做函数参数时,可以使用引用类型来减少拷贝对象的代价,尤其是避免容器的拷贝等。 但是当把函数内的局部对象当做返回值时,我们无法返回该局部对象的引用,导致每次返回局部对象都会进行拷贝。 因为返回局部对象的引用是无意义的,当函数调用完成,局部对象就被析构,所以其引用指向了一块析构的内存。
解决方案一: 将输出作为函数参数,例如:
void GetStudents(std::vector<int>* students) {
students->push_back(1);
students->push_back(2);
}
解决方案二: C++11的移动语义解决了这个问题。函数返回的临时变量被当做初始值或者赋值操作符右侧的运算对象时,程序使用移动操作,避免了拷贝,将新变量指向了局部变量的内容。例如:
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,临时对象。
测试实例:
#define _CRT_SECURE_NO_WARNINGS
#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) {
id_ = id;
size_t len = strlen(name);
name_ = new char[len + 1];
strcpy(name_, name);
}
// 注意:一个对象被移动之后,要确保其还能正常的进行析构函数。
~Student() {
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() {
Student student(123, "123");
return student;
}
int main() {
Student a(1, "1");
PrintStudent(a); // 调用拷贝构造
PrintStudent(Student(2, "2")); // 既没有移动构造也没有拷贝构造,而是直接将临时对象变为函数的参数。
PrintStudent(std::move(Student(2, "2"))); // 调用移动构造。
PrintStudent(std::move(a)); // 调用移动构造
a.Print(); // 原来的对象被移动。
Student a2 = GetStudent(); // 调用依次移动构造。
Student a3;
a3 = Student(3, "3"); // 调用移动赋值运算符。
Student a4(4, "4");
a3 = a4; // 调用拷贝构造函数。
a3 = std::move(a4); // 调用移动赋值运算符。
a3 = GetStudent(); // 调用移动赋值运算符。
return 0;
}
上一篇: 如何查看mysql是几位的
下一篇: 样式表css包括哪四种引入方式
推荐阅读
-
HTML5 b和i标记将被赋予真正的语义_html5教程技巧
-
简单的移动设备检测PHP脚本代码_php技巧
-
在使用@angular/cli创建的angular项目上添加postcss等一系列移动端自适应插件
-
html5适合移动应用开发的12大特性_html5教程技巧
-
jQuery实现div跟随鼠标移动的代码案例
-
如何实现移动端浏览器不显示 pc 端的广告_javascript技巧
-
分享Html技巧: 语义化你的代码
-
HTML5之外的语义标准(待完成)_html/css_WEB-ITnose
-
鼠标移动到图片名上,显示图片的简单实例_javascript技巧
-
移动端的自适应_html/css_WEB-ITnose