高效的类和结构的推送

时间:2010-10-29 18:40:20

标签: c++ stl stdvector

我见过我的同事经常做第二个片段。为什么是这样?我已经尝试添加打印语句来跟踪ctors和dtors,但两者看起来完全相同。

    std::vector<ClassTest> vecClass1;
    ClassTest ct1;
    ct1.blah = blah // set some stuff
    ...
    vecClass1.push_back(ct1);

    std::vector<ClassTest> vecClass2;
    vecClass2.push_back(ClassTest());
    ClassTest& ct2 = vecClass2.back();
    ct2.blah = blah // set some stuff
    ...

PS。如果标题有误导性,我很抱歉。

修改

首先,谢谢大家的回复。

我使用std::move编写了一个小应用程序。结果让我感到惊讶,也许是因为我做错了...有人会解释为什么“快速”路径表现得更好。

#include <vector>
#include <string>
#include <boost/progress.hpp>
#include <iostream>

const std::size_t SIZE = 10*100*100*100;
//const std::size_t SIZE = 1;
const bool log = (SIZE == 1);

struct SomeType {
    std::string who;
    std::string bio;
    SomeType() {
        if (log) std::cout << "SomeType()" << std::endl;
    }
    SomeType(const SomeType& other) {
        if (log) std::cout << "SomeType(const SomeType&)" << std::endl; 
        //this->who.swap(other.who);
        //this->bio.swap(other.bio);
        this->who = other.who;
        this->bio = other.bio;
    }
    SomeType& operator=(SomeType& other) {
        if (log) std::cout << "SomeType::operator=()" << std::endl;
        this->who.swap(other.who);
        this->bio.swap(other.bio);
        return *this;
    }
    ~SomeType() {
        if (log) std::cout << "~SomeType()" << std::endl;
    }
    void swap(SomeType& other) {
        if (log) std::cout << "Swapping" << std::endl;
        this->who.swap(other.who);
        this->bio.swap(other.bio);
    }
        // move semantics
    SomeType(SomeType&& other) : 
          who(std::move(other.who))
        , bio(std::move(other.bio)) {
        if (log) std::cout << "SomeType(SomeType&&)" << std::endl;
    }
    SomeType& operator=(SomeType&& other) {
        if (log) std::cout << "SomeType::operator=(SomeType&&)" << std::endl;
        this->who = std::move(other.who);
        this->bio = std::move(other.bio);
        return *this;
    }
};

int main(int argc, char** argv) {

    {
        boost::progress_timer time_taken;
        std::vector<SomeType> store;
        std::cout << "Timing \"slow\" path" << std::endl;
        for (std::size_t i = 0; i < SIZE; ++i) {
            SomeType some;
            some.who = "bruce banner the hulk";
            some.bio = "you do not want to see me angry";
            //store.push_back(SomeType());
            //store.back().swap(some);
            store.push_back(std::move(some));
        }
    }
    {
        boost::progress_timer time_taken;
        std::vector<SomeType> store;
        std::cout << "Timing \"fast\" path" << std::endl;
        for (std::size_t i = 0; i < SIZE; ++i) {
            store.push_back(SomeType());
            SomeType& some = store.back();
            some.who = "bruce banner the hulk";
            some.bio = "you do not want to see me angry";
        }
    }
    return 0;
}

输出:

dev@ubuntu-10:~/Desktop/perf_test$ g++ -Wall -O3 push_back-test.cpp -std=c++0x
dev@ubuntu-10:~/Desktop/perf_test$ ./a.out 
Timing "slow" path
3.36 s

Timing "fast" path
3.08 s

5 个答案:

答案 0 :(得分:7)

如果在“设置一些东西”后复制对象比以前更昂贵,那么当你在“设置一些东西”之前插入对象时,将对象插入向量时发生的复制会更便宜后。

但是,真的,因为你应该期望偶尔复制矢量中的对象,这可能不是一个优化。

答案 1 :(得分:3)

如果我们接受你的同事的片段是明智的,因为ClassTest复制起来很昂贵,我宁愿:

using std::swap;

std::vector<ClassTest> vecClass1;
ClassTest ct1;
ct1.blah = blah // set some stuff
...
vecClass1.push_back(ClassTest());
swap(ct1, vecClass1.back());

我认为它更清晰,而且可能更安全。 ...代码可能会分配资源,因此可能会抛出异常(或者是什么使得完全构建的ClassTest复制如此昂贵?)。因此,除非向量确实是函数的本地向量,否则我认为在运行该代码时将其作为半构造是个好主意。

如果ClassTest只有默认的swap实施,但如果ClassTest没有效率swap,那么这会更加昂贵,那么它就没有了复制的业务很昂贵。所以这个技巧也许应该只用于已知友好的类,而不是未知的模板参数类型。

正如Gene所说,如果你有C ++ 0x功能,std::move无论如何都会更好。

如果我们担心ClassTest复制成本很高,那么重新定位矢量是一个可怕的前景。所以我们也应该:

  • 在添加任何内容之前预留足够的空间,
  • 使用deque代替vector

答案 2 :(得分:2)

第二个版本受益于移动临时版。第一个版本是复制临时向量。所以第二个可能更快。第二个版本也有可能更小的峰值内存要求,第一个版本创建两个对象一个临时和一个副本,然后删除临时。您可以通过显式移动临时版来改进第一个版本:

std::vector<ClassTest> vecClass1;
ClassTest ct1;
ct1.blah = blah // set some stuff
...
vecClass1.push_back(std::move(ct1));

答案 3 :(得分:1)

你应该让你的同事知道完全为什么,但我们仍然可以猜测。正如詹姆斯指出的那样,如果一旦构造完成复制对象的成本更高,那么效率可能会提高一些。

我看到两个版本都有优势。

我喜欢你的同事的片段因为:虽然在这两种情况下都有2个对象,但它们只在第二个版本中共存很短的时间。只有一个对象可供编辑:这可以避免在ct1之后编辑push_back的潜在错误。

我喜欢您的个人代码段,因为:调用push_back添加第二个对象可能使引用ct2无效,从而导致未定义的风险行为。第一个片段不存在这种风险。

答案 4 :(得分:0)

它们完全相同(据我所知)。也许他或她这样做是惯用的习惯。