C++11研究vector插入元素尽量减少对象构造

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yockie/article/details/79038780

在阅读之前可以可以先看这篇文章复习一下std::move与std::forward:
理解std::move和std::forward

背景

遇到以下场景,Bar类中有成员变量foo是Foo类的对象,在项目代码中需要申请一个Bar对象,给其中foo中的变量复制,并插入到vector< Bar >中,定义如下:

struct Foo {
    std::string name;
    std::string id;
    Foo() {
        std::cout<<"Foo construct"<<std::endl;
        printf("%p\n", this);
    }
    ~Foo() {
        std::cout<<"Foo deconstruct------"<<std::endl;
        printf("%p\n", this);
    }
    Foo(const Foo& f) {
        std::cout<<"Foo copy construct"<<std::endl;
        printf("%p\n", this);
    }
    Foo(Foo&& f) {
        name = std::move(f.name);
        id = std::move(f.id);
        std::cout<<"Foo move construct"<<std::endl;
        printf("%p\n", this);
    }
    Foo& operator= (Foo&& f) {
        std::cout<<"Foo move operator"<<std::endl;
        return *this;
    }
};

struct Bar {
    std::string* key;
    Foo foo;
    Bar(std::string* k, Foo&& f) :
        key(k), foo(std::forward<Foo>(f)) {
        std::cout<<"Bar move construct1"<<std::endl;
    }
    Bar(Bar && bar) {
        std::cout<<"Bar move construct2"<<std::endl;
    }
    Bar(const Bar & bar) {
        std::cout<<"Bar copy construct"<<std::endl;
    }
    Bar() : key(nullptr) {
        std::cout<<"Bar default construct"<<std::endl;
    }
    Bar(std::string* k) : key(k) {
        std::cout<<"Bar construct"<<std::endl;
    }

    ~Bar() {
        std::cout<<"Bar deconstruct***************"<<std::endl;
    }
};

int main(int argc, const char * argv[]) {

    std::vector<Bar> vec;

    //预先准备的数据,不考虑优化这3个值的复制操作
    std::string key = "key";
    std::string name = "yockie";
    std::string id = "616";

    //下文仅修改此域代码:
    {
        //vec中插入一个Bar,使Bar的key为&key,foo的name、id分别为上述的name、id,如何尽量减少对象拷贝?
    }

    return 0;
}

问题:

上面代码中已经提出了,vec中插入一个Bar,使Bar的key为&key,foo的name、id分别为上述的name、id,如何尽量减少对象拷贝?

方法一:

最直观的方法:

{
        Bar bar;
        bar.key = &key;
        bar.foo.name = name;
        bar.foo.id = id;

        vec.push_back(bar);
}

打印结果,并逐行解释:

//进入Bar(),先构造成员变量foo,所以进入了Foo(),所以打印了以下2行
Foo construct
0x7fff5fbff3a8
//成员变量构造完成,进入Bar()所以打印以下这行
Bar default construct
//push_back要构造新的Bar对象,进入Bar(const Bar & bar),同样先构造成员变量foo,所以进入了Foo(),所以打印了以下2行
Foo construct
0x100446c18
//成员变量构造完成,进入Bar(const Bar & bar)所以打印以下这行
Bar copy construct
//退出子域,释放Bar bar;对象,先进入Bar析构函数体:
Bar deconstruct***************
//再释放各成员变量内存
Foo deconstruct------
0x7fff5fbff3a8
//退出main函数,释放vector空间,先释放各元素空间,同样先进入Bar析构函数体:
Bar deconstruct***************
//再释放各成员变量内存
Foo deconstruct------
0x100446c18

方法二:

{
        Bar bar;
        bar.key = &key;
        bar.foo.name = name;
        bar.foo.id = id;

        vec.push_back(std::move(bar));
}

打印结果,及差异部分的解释:

//以下3行同上相同
Foo construct
0x7fff5fbff390
Bar default construct
//std::move把bar从左值转为右值,进而调用Bar(Bar && bar)而不是Bar(const Bar & bar),先构造成员变量foo:
Foo construct
0x100701ac8
//再进入Bar(Bar && bar)函数体,这里构造的Bar对象是vector中是元素,所以后面释放两个bar对象,先释放子域中的bar对象
Bar move construct2
Bar deconstruct***************
Foo deconstruct------
0x7fff5fbff390
//再释放vector中的bar对象
Bar deconstruct***************
Foo deconstruct------
0x100701ac8

上述例子充分说明一点,std::move并不会”移动“内存,只是把左值转为右值,进而决定使用哪一种(这里是)构造函数,和方法一一样会构造新对象。

方法3:

{
        Foo foo;
        foo.name = name;
        foo.id = id;

        vec.emplace_back(&key, std::move(foo));
}

打印结果,及差异部分解释:

//子域中foo对象构造
Foo construct
0x7fff5fbff348
//emplace_back中的参数将调用Bar的隐式构造函数Bar(std::string* k, Foo&& f),后者的初始化列表调用std::forward将f从左值转为右值,进而选择调用foo的移动构造函数Foo(Foo&& f):
Foo move construct
0x100756c78
//再进入Bar(std::string* k, Foo&& f)函数体
Bar move construct1
//释放子域foo对象
Foo deconstruct------
0x7fff5fbff348
//释放vector成员内存,先进入析构函数体
Bar deconstruct***************
//再释放成员变量内存
Foo deconstruct------
0x100756c78

上述例子也充分说明一点,std::forward也不会”移动“内存,只是在特定条件下把左值转为右值,进而决定使用哪一种(这里是)构造函数,和方法一一样会构造新对象。

方法四:

{
            vec.emplace_back(&key);
            vec[0].foo.name = name;
            vec[0].foo.id = name;
}

打印结果,及解释:

//emplace_back中的参数将调用Bar的隐式构造函数Bar(std::string* k),先构造成员函数:
Foo construct
0x10044e4c8
//进入Bar(std::string* k)函数体
Bar construct
//退出main函数前释放vector内存,先释放插入的元素的内存
Bar deconstruct***************
Foo deconstruct------
0x10044e4c8

方法四充分利用了emplace_back利用临时对象内存、不构造新对象的特点,Bar和Foo对象仅构造和释放了一次,已经相当优化了。

猜你喜欢

转载自blog.csdn.net/yockie/article/details/79038780