emplace_back和move的简单实践

readme

最近参与了一个性能优化相关的项目,感觉应该对emplace_back和move做个总结。本文比较浅,没有过于高深的理论,高手们不必浪费时间,初学者可以参考下,省的浪费时间踩坑。内附可编译通过的例程。

简介

emplace_back和move,已算不上新特性。但怎么才能正确用出它俩的优势,可能未必每个人都能说明白,我也说不明白,谁没事儿有功夫看标准库源码呀,有时间也未必看的懂呀TT。
本质上讲,push_back操作引入了一些临时变量的构造析构和拷贝,emplace_back可以配合着起到“原地构造”的效果。还有就是移动语义move,可以通过类似指针指向新的地方,来达到避免开辟一块新的内存然后再拷贝。看代码吧。

测试程序

//common.h
#ifndef _COMMON_H_
#define _COMMON_H_

#include <string>
#include <string.h>
#include <stdio.h>
#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 <set>
#include <list>
#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("constructed by default construct func.\n");
        }
    }
    TestClass(const TestClass& other): _testStr(other._testStr), _needPrintBool(other._needPrintBool) {
        if (_needPrintBool) {
            printf("constructed by copy construct func.\n");
        }
    }
    TestClass(TestClass&& other): _testStr(std::move(other._testStr)), _needPrintBool(other._needPrintBool) {
        if (_needPrintBool) {
            printf("constructed by move construct func.\n");
        }
    }
    ~TestClass() {
        if (_needPrintBool) {
            printf("Being destructed.\n");
        }
    }

    string& get_var() {
        return _testStr;
    }

private:
    string _testStr;
    bool _needPrintBool;
};

int main(void) {
    string testStr("TextClass member var content");
    {
        printf("in section 1\n");
        printf("default 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("\nin section 2\n");
        TestClass testClass1(testStr, true);
        TestClass testClass2(testStr, true);
        vector<TestClass> testVec;
        testVec.reserve(8);
        printf("push_back一个已生成的对象\n");
        testVec.push_back(testClass1);
        printf("push_back一个已生成的对象的右值引用\n");
        testVec.push_back(std::move(testClass1));
        printf("push_back一个现生成的对象\n");
        testVec.push_back(TestClass(testStr, true));
        printf("emplace_back一个已生成的对象\n");
        testVec.emplace_back(testClass2);
        printf("emplace_back一个已生成的对象的右值引用\n");
        testVec.emplace_back(std::move(testClass2));
        printf("emplace_back一个现生成的对象\n");
        testVec.emplace_back(testStr, true);
        printf("section 2 finished\n");
    }

    {
        vector<TestClass> testVec;
        testVec.reserve(1024);
        printf("\nin 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("\nin section 4\n");
        Timer count("emplace_back", true);
        for (size_t i = 0; i < 1024; ++i) {
            testVec.emplace_back(testStr, false);
        }
    }

    return 0;
}

编译和执行结果

g++ emplace_test.cpp -o emplace_test -std=c++11 -O0
./emplace_test
in section 1
default construct
constructed by default construct func.
copy construct
constructed by copy construct func.
move construct
constructed by move construct func.
testClass1.get_var():
testClass2.get_var(): TextClass member var content
testClass3.get_var(): TextClass member var content
Being destructed.
Being destructed.
Being destructed.

in section 2
constructed by default construct func.
constructed by default construct func.
push_back一个已生成的对象
constructed by copy construct func.
push_back一个已生成的对象的右值引用
constructed by move construct func.
push_back一个现生成的对象
constructed by default construct func.
constructed by move construct func.
Being destructed.
emplace_back一个已生成的对象
constructed by copy construct func.
emplace_back一个已生成的对象的右值引用
constructed by move construct func.
emplace_back一个现生成的对象
constructed by default construct func.
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 60us

in section 4
emplace_back, cost 43us

测试程序解释

  • testVec.reserve(8);这一句很重要,不然后面向vector中添加东西的时候vector会扩容并且拷贝元素。
  • printf(“testClass1.get_var(): %s\n”, testClass1.get_var().c_str());这一行严格讲是有问题的,因为testClass1已经被移动了,其值不再可靠。
  • testVec.push_back(testClass1);执行一次拷贝构造函数。
  • testVec.push_back(std::move(testClass1));执行一次移动构造函数。
  • testVec.push_back(TestClass(testStr, true));执行一次默认构造函数和一次移动构造函数。最笨的一种写法。
  • testVec.emplace_back(testClass2);执行一次拷贝构造函数,和testVec.push_back(testClass1);一样,所以说别迷信啦,不是把push换成emplace就行。
  • testVec.emplace_back(std::move(testClass2));执行一次移动构造函数,和testVec.push_back(std::move(testClass1));一样,所以说别迷信啦+1。
  • testVec.emplace_back(testStr, true);执行一次默认构造函数。

总结

尝试总结一下。

  • 如果向vector添加一个元素,添加完了之后这个元素在vector外面就不再需要了,那么testVec.emplace_back(testStr, true);的写法最高效,直接“本地构造”一个元素,不涉及临时变量的构造、析构或拷贝。push_back不支持这种写法。
  • 如果有一个元素已经构造出来,希望添加进vector,而且后续这个元素不再被需要,那么emplace_back和push_back都一样,重点是std::move的使用。
  • 不可忽视一次拷贝和一次移动拷贝之间的区别,代码中_testStr(std::string)作为成员变量之一,其拷贝构造和移动构造有本质区别。在最后两个section可看出效率方面的差异。
  • 可以换一种思路,push_back能做的事emplace_back都能做,emplace_back仅在有机会“原地构造”的时候有可能发挥不一样的作用。
  • 想用上移动语义的话,需要自己实现移动构造函数(例程中“TestClass(TestClass&& other)”),不然会退化到默认构造函数。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值