2

私の同僚が 2 番目のスニペットを頻繁に実行しているのを見てきました。どうしてこれなの?ctors と dtors を追跡するために print ステートメントを追加しようとしましたが、どちらも同じように見えます。

    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 のコピーにコストがかかることを懸念している場合、ベクトルの再配置は恐ろしい見通しです。したがって、次のいずれかも行う必要があります。

  • 何かを追加する前に十分なスペースを確保し、
  • dequeの代わりに を使用しvectorます。
于 2010-10-29T19:38:16.097 に答える
2

2 番目のバージョンは、一時的なものを移動することでメリットがあります。最初のバージョンは、一時的なベクターをコピーしています。したがって、2 番目の方が潜在的に高速です。2 番目のバージョンでは、ピーク時のメモリ要件が小さくなる可能性もあります。最初のバージョンでは、一時オブジェクトとそのコピーを 1 つずつ作成してから、一時オブジェクトを削除します。一時的なものを明示的に移動することで、最初のバージョンを改善できます。

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

おそらく同僚に理由を正確に知ってもらう必要がありますが、それでも推測することはできます。ジェームズが指摘したように、一度構築されたオブジェクトをコピーするのに費用がかかる場合は、少し効率的かもしれません。

どちらのバージョンにも利点があります。

私はあなたの同僚のスニペットが好きです。なぜなら、どちらの場合も2つのオブジェクトがありますが、2番目のバージョンでは非常に短い期間しか共存しないからです。ct1編集に使用できるオブジェクトは1つだけです。これにより、後の編集で発生する可能性のあるエラーを回避できますpush_back

私はあなたの個人的なスニペットが好きです。なぜならpush_back、2番目のオブジェクトを追加するために呼び出すと、参照が無効になり、未定義の動作のリスクが生じる可能性があるからです。ct2最初のスニペットはこのリスクを示していません。

于 2010-10-29T18:55:13.833 に答える
0

それらは同一です(私が見る限り)。たぶん、彼または彼女はそれを慣用的な習慣としてやっているのでしょう。

于 2010-10-29T18:45:03.913 に答える