2

我经常看到我的同事做第二个片段。为什么是这样?我尝试添加打印语句来跟踪 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
4

5 回答 5

7

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

但是,实际上,由于您应该期望偶尔复制向量中的对象,因此这可能不是什么优化。

于 2010-10-29T18:44:30.853 回答
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 所说,std::move无论如何,如果你有 C++0x 特性,那就更好了。

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

  • 在添加任何东西之前保留足够的空间,
  • 使用 adeque而不是 a vector
于 2010-10-29T19:38:16.097 回答
2

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

std::vector<ClassTest> vecClass1;
ClassTest ct1;
ct1.blah = blah // set some stuff
...
vecClass1.push_back(std::move(ct1));
于 2010-10-29T19:00:58.910 回答
1

您可能应该问您的同事确切原因,但我们仍然可以猜测。正如 James 指出的那样,如果对象在构造后复制成本更高,则效率可能会更高。

我看到了两个版本的优势。

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

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

于 2010-10-29T18:55:13.833 回答
0

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

于 2010-10-29T18:45:03.913 回答