readme
移动语义是c++11中的新特性,它的核心理念是所有权的转移,而非传统意义上的复制。许多c++11模板库,例如string,vector都支持移动语义,都包含移动构造函数。移动语义通常和右值引用一同讨论,下面语句中:
string s, s1, s2;
s = s1 + s2;
如果不考虑编译器做的优化,在c++11之前,s1 + s2的结果会被保存在一个新创建的临时string对象中,然后临时对象的内容会取代s原来的内容,最后临时对象和s原来的内容会被释放。在整个过程中,临时对象没有名字,在语句结束之后会被销毁。在c++11中,这个临时对象就是一个右值,std::move(s1 + s2)会创建一个指向临时对象内容的右值引用。移动语义这一特性,让之前大量存在的临时变量或临时对象可以发挥更多的作用,从而达到性能优化的目的。
刚刚描述的case在c++11中并不是事实,编译器针对s1 + s2这种语句已经做了优化,不需要显示的std::move(s1 + s2)来转换。刚刚的描述仅仅为了尽量简单直观的解释移动语义和右值引用。
移动语义并不是什么黑科技,但若想利用移动语义带来的优化效果,很有必要细致的学习相关知识。这篇博客不会详细介绍移动语义,这篇博客用一个简单的例子直观表达了移动语义可以带来的优化效果。
测试程序
//common.h
#ifndef _COMMON_H_
#define _COMMON_H_
#include <string>
#include <stdio.h>
#include <set>
#include <memory>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <utility>
#include <algorithm>
#include <map>
#include <numeric>
#include <vector>
#include <iterator>
#include <sys/time.h>
using namespace std;
#endif
#ifndef _TIMER_H
#define _TIMER_H
#include <sys/time.h>
#include "common.h"
class Timer
{
public:
Timer(string strCallInfo = "Timer", bool us_used = false)
{
_desc = strCallInfo;
_us_used = us_used;
gettimeofday(&_begin, NULL);
gettimeofday(&_end, NULL);
};
~Timer()
{
gettimeofday(&_end,NULL);
if (_us_used)
{
printf("%s, cost %ldus\n", _desc.c_str(), (_end.tv_sec - _begin.tv_sec) * 1000000 + _end.tv_usec - _begin.tv_usec);
}
else
{
printf("%s, cost %ldms\n", _desc.c_str(), (_end.tv_sec - _begin.tv_sec) * 1000 + (_end.tv_usec - _begin.tv_usec) / 1000);
}
};
private:
Timer(){};
string _desc;
bool _us_used;
timeval _begin;
timeval _end;
};
#endif
#include "common.h"
#include "Timer.h"
const int swapTimesInt = 1024;
const int stringLenInt = 1024;
//普通swap函数
void common_swap(string &subString1, string &subString2){
string tmpString(subString1);
subString1 = subString2;
subString2 = tmpString;
}
//使用移动语义的swap函数
void move_swap(string &subString1, string &subString2){
string tmpString(std::move(subString1));
subString1 = std::move(subString2);
subString2 = std::move(tmpString);
}
int main(void){
string subString1;
string subString2;
for (int i = 0; i < stringLenInt; i++){
char stringEleChar[stringLenInt] = {0};
snprintf(stringEleChar, stringLenInt, "%d", i);
subString1 += stringEleChar;
snprintf(stringEleChar, stringLenInt, "_%d", i);
subString2 += stringEleChar;
}
printf("size of subString1: %ld\n", subString1.length());
printf("size of subString2: %ld\n", subString2.length());
{
Timer timer("common swap", true);
for (long int i = 0; i < swapTimesInt; i++){
common_swap(subString1, subString2);
}
}
{
Timer timer("move swap", true);
for (long int i = 0; i < swapTimesInt; i++){
move_swap(subString1, subString2);
}
}
return 0;
}
编译
g++ -o swap_move swap_move.cpp -O0 -g -Wall -std=c++11
执行
[root@***]# ./swap_move
size of subString1: 2986
size of subString2: 4010
common swap, cost 67us
move swap, cost 24us
总结
测试程序中,首先构造两个string对象,然后用不同版本的swap函数分别进行1024次交换,最后打印时间。从时间结果来看,优化的效果很明显,但通过更多测试可以发现,优化效果取决于字符串的大小,也取决于编译器的设计。
移动语义使用过程中要十分仔细,比如std::move(s)执行之后,s不可被继续使用,s必须保证可以被析构。
移动语义是很新颖的特性,值得仔细学习,这篇博客会不定期更新。