readme
考虑这样的语句:
vector<string> testVec;
testVec.push_back(string(16, 'a'));
上述语句足够简单易懂,将一个string对象添加到testVec中。但是底层发生的事情却未必每个程序员都能说清楚。首先,string(16, ‘a’)会创建一个string类型的临时对象,这涉及到一次string构造过程。其次,vector内会创建一个新的string对象,这是第二次构造。最后在push_back结束时,最开始的临时对象会被析构。加在一起,这两行代码会涉及到两次string构造和一次析构。
c++11可以用emplace_back代替push_back,emplace_back可以直接在vector中构建一个对象,而非创建一个临时对象,再放进vector,再销毁。emplace_back可以省略一次构建和一次析构,从而达到优化的目的。
PS,这边博客还涉及到一些移动语义的知识。
测试程序
//common.h
#ifndef _COMMON_H_
#define _COMMON_H_
#include <string>
#include <stdio.h>
#include <set>
#include <memory>
#include <stdlib.h>
#include <unistd.h>
#include <jsoncpp/json/json.h>
#include <iostream>
#include <librdkafka/rdkafka.h>
#include <utility>
#include <algorithm>
#include <map>
#include <numeric>
#include <vector>
#include <iterator>
#include <sys/time.h>
#include <math.h>
using namespace std;
#endif
//Timer.h
#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
//emplace_test.cpp
#include "common.h"
#include "Timer.h"
class TestClass
{
public:
TestClass(string testStr, bool needPrintBool): _testStr(std::move(testStr)), _needPrintBool(needPrintBool)
{
_needPrintBool = needPrintBool;
if (needPrintBool)
{
printf("Being constructed.\n");
}
}
TestClass(const TestClass& other): _testStr(other._testStr), _needPrintBool(other._needPrintBool)
{
if (_needPrintBool)
{
printf("Being copy constructed.\n");
}
}
TestClass(TestClass&& other): _testStr(std::move(other._testStr)), _needPrintBool(other._needPrintBool)
{
if (_needPrintBool)
{
printf("Being moved constructed.\n");
}
}
~TestClass()
{
if (_needPrintBool)
{
printf("Being destructed.\n");
}
}
string& get_var()
{
return _testStr;
}
private:
string _testStr;
bool _needPrintBool;
};
int main(void)
{
string testStr(128, 'a');
{
printf("in section 1\n");
printf("construct\n");
TestClass testClass1(testStr, true);
printf("copy construct\n");
TestClass testClass2(testClass1);
printf("move construct\n");
TestClass testClass3(std::move(testClass1));
printf("testClass1.get_var(): %s\n", testClass1.get_var().c_str());
printf("testClass2.get_var(): %s\n", testClass2.get_var().c_str());
printf("testClass3.get_var(): %s\n", testClass3.get_var().c_str());
}
{
printf("in section 2\n");
TestClass testClass1(testStr, true);
TestClass testClass2(testStr, true);
vector<TestClass> testVec[6];
printf("push_back一个已生成的对象\n");
testVec[0].push_back(testClass1);
printf("push_back一个已生成的对象的右值引用\n");
testVec[1].push_back(std::move(testClass1));
printf("push_back一个现生成的对象\n");
testVec[2].push_back(TestClass(testStr, true));
printf("emplace_back一个已生成的对象\n");
testVec[3].emplace_back(testClass2);
printf("emplace_back一个已生成的对象的右值引用\n");
testVec[4].emplace_back(std::move(testClass2));
printf("emplace_back一个现生成的对象\n");
testVec[5].emplace_back(testStr, true);
printf("section 2 finished\n");
}
{
vector<TestClass> testVec;
testVec.reserve(1024);
printf("in section 3\n");
Timer count("push_back", true);
for (size_t i = 0; i < 1024; ++i)
{
testVec.push_back(TestClass(testStr, false));
}
}
{
vector<TestClass> testVec;
testVec.reserve(1024);
printf("in section 4\n");
Timer count("emplace_back", true);
for (size_t i = 0; i < 1024; ++i)
{
testVec.emplace_back(testStr, false);
}
}
return 0;
}
编译
g++ -o emplace_test emplace_test.cpp -O0 -g -Wall -std=c++11
运行结果
[root@***]# ./emplace_test
in section 1
construct
Being constructed.
copy construct
Being copy constructed.
move construct
Being moved constructed.
testClass1.get_var():
testClass2.get_var(): aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
testClass3.get_var(): aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Being destructed.
Being destructed.
Being destructed.
in section 2
Being constructed.
Being constructed.
push_back一个已生成的对象
Being copy constructed.
push_back一个已生成的对象的右值引用
Being moved constructed.
push_back一个现生成的对象
Being constructed.
Being moved constructed.
Being destructed.
emplace_back一个已生成的对象
Being copy constructed.
emplace_back一个已生成的对象的右值引用
Being moved constructed.
emplace_back一个现生成的对象
Being constructed.
section 2 finished
Being destructed.
Being destructed.
Being destructed.
Being destructed.
Being destructed.
Being destructed.
Being destructed.
Being destructed.
in section 3
push_back, cost 62us
in section 4
emplace_back, cost 44us
测试程序详解
TestClass类
TestClass是一个类,包含构造函数,拷贝构造函数,移动构造函数,析构函数,以及一个返回私有成员变量_testStr的成员函数。TestClass类还包含了另一个bool型私有成员变量,用于控制是否打印各个构造函数和析构函数里面的信息。
section 1
分别用普通构造函数,拷贝构造函数,移动构造函数创建了3个类。可以通过打印的信息看出各个构造函数和析构函数被调用的情况。并且在最后的三条打印信息中可以看出,在“TestClass testClass3(std::move(testClass1));”语句中被移动之后的testClass1的_testStr为空。
section 2
分别执行了不同场景的push_back和emplace_back,通过打印信息可以看到底层构造和析构函数的调用情况。
section 2中使用了vector数组,因为如果使用同一个vector,在不断的加入新元素的过程中,会伴随着开辟新内存,搬移老元素的情况,这里的搬移本质上讲就是拷贝构造,会打印出干扰信息。
- push_back一个已经生成的对象。与readme中介绍的场景不同,不需要创建临时变量,仅调用一次拷贝构造函数。
- push_back一个已生成的对象的右值引用。比较高效,这里使用的是接受右值引用为形参的push_back重载版本。
- push_back一个现生成的对象。如readme中介绍,通过一次普通构造函数生成一个临时变量,一次移动构造函数对vector内的元素赋值,最后一次析构函数销毁临时变量。
- emplace_back一个已生成的对象。与“push_back一个已经生成的对象”行为一致,调用一次拷贝构造函数。所以,如果希望将一个已经生成的对象添加到vector,push_back和emplace_back行为一致,并未起到优化效果。
- emplace_back一个已生成的对象的右值引用。比较高效,仅仅通过移动构造函数在vector指定位置构造了一个元素。
- emplace_back一个现生成的对象。比较高效,仅仅通过普通构造函数在vector指定位置构造了一个元素。
section 3&4
分别向vector中push_back和emplace_back了1024个元素,然后打印和对比耗时。由耗时数据可以看到,emplace_back更为高效。
总结
置入语义还有其他用法,也不仅仅能作用于vector,但是原理都是相同的,当处理的数据量达到一定程度,这种平时看上去不起眼的优化动作往往会被放大到不能忽略的地步。