理解和使用c++11置入(emplace_back)

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,但是原理都是相同的,当处理的数据量达到一定程度,这种平时看上去不起眼的优化动作往往会被放大到不能忽略的地步。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值