C++11新特性之 Move semantics(移动语义)
2015-11-09 23:57
323 查看
按值传递的意义是什么?
当一个函数的参数按值传递时,这就会进行拷贝。当然,编译器懂得如何去拷贝。
而对于我们自定义的类型,我们也许需要提供拷贝构造函数。
但是不得不说,拷贝的代价是昂贵的。
所以我们需要寻找一个避免不必要拷贝的方法,即C++11提供的移动语义。
上一篇博客中有一个句话用到了:
实际上,右值引用注意用于创建移动构造函数和移动赋值运算。
移动构造函数类似于拷贝构造函数,把类的实例对象作为参数,并创建一个新的实例对象。
但是 移动构造函数可以避免内存的重新分配,因为我们知道右值引用提供了一个暂时的对象,而不是进行copy,所以我们可以进行移动。
换言之,在设计到关于临时对象时,右值引用和移动语义允许我们避免不必要的拷贝。我们不想拷贝将要消失的临时对象,所以这个临时对象的资源可以被我们用作于其他的对象。
右值就是典型的临时变量,并且他们可以被修改。如果我们知道一个函数的参数是一个右值,我们可以把它当做一个临时存储。这就意味着我们要移动而不是拷贝右值参数的内容。这就会节省很多的空间。
说多无语,看代码:
移动构造函数
语法:
最主要的是没有用到新的资源,是移动而不是拷贝。
假设一个地址指向了一个有一百万个int元素的数组,使用move构造函数,我们没有创造什么,所以代价很低。
移动比拷贝更快!!!
移动赋值运算符
语法:
工作流程这样的:Google上这么说的:
Release any resources that *this currently owns.
Pilfer other’s resource.
Set other to a default state.
Return *this.
让我们看几个move带来的好处吧!
vector众所周知,C++11后对vector也进行了一些优化。例如vector::push_back()被定义为了两种版本的重载,一个是cosnt T&左值作为参数,一个是T&&右值作为参数。例如下面的代码:
上面两个push_back()都会调用push_back(T&&)版本,因为他们的参数为右值。这样提高了效率。
而 当参数为左值的时候,会调用push_back(const T&) 。
但事实我们可以使用 static_cast进行强制:
我们可以使用std::move完成上面的任务:
似乎push_back(T&&)永远是最佳选择,但是一定要记住:
push_back(T&&) 使得参数为空。如果我们想要保留参数的值,我们这个时候需要使用拷贝,而不是移动。
最后写一个例子,看看如何使用move来交换两个对象:
当一个函数的参数按值传递时,这就会进行拷贝。当然,编译器懂得如何去拷贝。
而对于我们自定义的类型,我们也许需要提供拷贝构造函数。
但是不得不说,拷贝的代价是昂贵的。
所以我们需要寻找一个避免不必要拷贝的方法,即C++11提供的移动语义。
上一篇博客中有一个句话用到了:
#include <iostream> void f(int& i) { std::cout << "lvalue ref: " << i << "\n"; } void f(int&& i) { std::cout << "rvalue ref: " << i << "\n"; } int main() { int i = 77; f(i); // lvalue ref called f(99); // rvalue ref called f(std::move(i)); // 稍后介绍 return 0; }
实际上,右值引用注意用于创建移动构造函数和移动赋值运算。
移动构造函数类似于拷贝构造函数,把类的实例对象作为参数,并创建一个新的实例对象。
但是 移动构造函数可以避免内存的重新分配,因为我们知道右值引用提供了一个暂时的对象,而不是进行copy,所以我们可以进行移动。
换言之,在设计到关于临时对象时,右值引用和移动语义允许我们避免不必要的拷贝。我们不想拷贝将要消失的临时对象,所以这个临时对象的资源可以被我们用作于其他的对象。
右值就是典型的临时变量,并且他们可以被修改。如果我们知道一个函数的参数是一个右值,我们可以把它当做一个临时存储。这就意味着我们要移动而不是拷贝右值参数的内容。这就会节省很多的空间。
说多无语,看代码:
#include <iostream> #include <algorithm> class A { public: // Simple constructor that initializes the resource. explicit A(size_t length) : mLength(length), mData(new int[length]) { std::cout << "A(size_t). length = " << mLength << "." << std::endl; } // Destructor. ~A() { std::cout << "~A(). length = " << mLength << "."; if (mData != NULL) { std::cout << " Deleting resource."; delete[] mData; // Delete the resource. } std::cout << std::endl; } // Copy constructor. A(const A& other) : mLength(other.mLength), mData(new int[other.mLength]) { std::cout << "A(const A&). length = " << other.mLength << ". Copying resource." << std::endl; std::copy(other.mData, other.mData + mLength, mData); } // Copy assignment operator. A& operator=(const A& other) { std::cout << "operator=(const A&). length = " << other.mLength << ". Copying resource." << std::endl; if (this != &other) { delete[] mData; // Free the existing resource. mLength = other.mLength; mData = new int[mLength]; std::copy(other.mData, other.mData + mLength, mData); } return *this; } // Move constructor. A(A&& other) : mData(NULL), mLength(0) { std::cout << "A(A&&). length = " << other.mLength << ". Moving resource.\n"; // Copy the data pointer and its length from the // source object. mData = other.mData; mLength = other.mLength; // Release the data pointer from the source object so that // the destructor does not free the memory multiple times. other.mData = NULL; other.mLength = 0; } // Move assignment operator. A& operator=(A&& other) { std::cout << "operator=(A&&). length = " << other.mLength << "." << std::endl; if (this != &other) { // Free the existing resource. delete[] mData; // Copy the data pointer and its length from the // source object. mData = other.mData; mLength = other.mLength; // Release the data pointer from the source object so that // the destructor does not free the memory multiple times. other.mData = NULL; other.mLength = 0; } return *this; } // Retrieves the length of the data resource. size_t Length() const { return mLength; } private: size_t mLength; // The length of the resource. int* mData; // The resource. };
移动构造函数
语法:
A(A&& other) noexcept // C++11 - specifying non-exception throwing functions { mData = other.mData; // shallow copy or referential copy other.mData = nullptr; }
最主要的是没有用到新的资源,是移动而不是拷贝。
假设一个地址指向了一个有一百万个int元素的数组,使用move构造函数,我们没有创造什么,所以代价很低。
// Move constructor. A(A&& other) : mData(NULL), mLength(0) { // Copy the data pointer and its length from the // source object. mData = other.mData; mLength = other.mLength; // Release the data pointer from the source object so that // the destructor does not free the memory multiple times. other.mData = NULL; other.mLength = 0; }
移动比拷贝更快!!!
移动赋值运算符
语法:
A& operator=(A&& other) noexcept { mData = other.mData; other.mData = nullptr; return *this; }
工作流程这样的:Google上这么说的:
Release any resources that *this currently owns.
Pilfer other’s resource.
Set other to a default state.
Return *this.
// Move assignment operator. A& operator=(A&& other) { std::cout << "operator=(A&&). length = " << other.mLength << "." << std::endl; if (this != &other) { // Free the existing resource. delete[] mData; // Copy the data pointer and its length from the // source object. mData = other.mData; mLength = other.mLength; // Release the data pointer from the source object so that // the destructor does not free the memory multiple times. other.mData = NULL; other.mLength = 0; } return *this; }
让我们看几个move带来的好处吧!
vector众所周知,C++11后对vector也进行了一些优化。例如vector::push_back()被定义为了两种版本的重载,一个是cosnt T&左值作为参数,一个是T&&右值作为参数。例如下面的代码:
std::vector<A> v; v.push_back(A(25)); v.push_back(A(75));
上面两个push_back()都会调用push_back(T&&)版本,因为他们的参数为右值。这样提高了效率。
而 当参数为左值的时候,会调用push_back(const T&) 。
#include <vector> int main() { std::vector<A> v; A aObj(25); // lvalue v.push_back(aObj); // push_back(const T&) }
但事实我们可以使用 static_cast进行强制:
// calls push_back(T&&) v.push_back(static_cast<A&&>(aObj));
我们可以使用std::move完成上面的任务:
v.push_back(std::move(aObj)); //calls push_back(T&&)
似乎push_back(T&&)永远是最佳选择,但是一定要记住:
push_back(T&&) 使得参数为空。如果我们想要保留参数的值,我们这个时候需要使用拷贝,而不是移动。
最后写一个例子,看看如何使用move来交换两个对象:
#include <iostream> using namespace std; class A { public: // constructor explicit A(size_t length) : mLength(length), mData(new int[length]) {} // move constructor A(A&& other) { mData = other.mData; mLength = other.mLength; other.mData = nullptr; other.mLength = 0; } // move assignment A& operator=(A&& other) noexcept { mData = other.mData; mLength = other.mLength; other.mData = nullptr; other.mLength = 0; return *this; } size_t getLength() { return mLength; } void swap(A& other) { A temp = move(other); other = move(*this); *this = move(temp); } int* get_mData() { return mData; } private: int *mData; size_t mLength; }; int main() { A a(11), b(22); cout << a.getLength() << ' ' << b.getLength() << endl; cout << a.get_mData() << ' ' << b.get_mData() << endl; swap(a,b); cout << a.getLength() << ' ' << b.getLength() << endl; cout << a.get_mData() << ' ' << b.get_mData() << endl; return 0; }
相关文章推荐
- C++11新特性之 Move semantics(移动语义)
- C++ - 异常处理:try,catch,throw,finally的用法
- <转载与补充>C与C++动态内存分配
- C语言指针学习
- [挖坑]const限定符
- C++ erase的用法
- 数组作为函数形参的一点总结
- effective c++尽可能延后变量定义式的出现时间
- [C++渐进]引用与指针
- C/Cplusplus
- 什么是bind函数,为什么要使用bind函数
- 全错位排列问题
- lambda表达式的实质——什么是lambda表达式,为什么要使用lambda表达式
- 回调函数的实质——什么是回调函数,为什么要使用回调函数
- C语言编写年月日格式时间转换为格林威治时间
- C++ 中通过函数名字的字符串调用函数
- C++ 中通过函数名字的字符串调用函数
- c++多态性原理及应用 & c++以及cuda 计时技术小结
- 如何学好C++语言
- 在VC++中创建的项目中添加一个资源文件