13

私はまだ初心者のプログラマーです。時期尚早の最適化が悪いことは知っていますが、巨大なものをあちこちにコピーすることも悪いことだと知っています。

私はコピー省略について読みましたが、それは同義語ですが、たとえばウィキペディアの例では、コピー省略は、返されるオブジェクトが完全に構築されると同時に返された場合にのみ発生するように思えます。

ベクトルのようなオブジェクトは、通常、何かで満たされた場合にのみ意味を持ち、戻り値として使用されます。結局のところ、空のベクターは手動でインスタンス化できます。

では、このような場合でも機能しますか?

簡潔にするための悪いスタイル:

vector<foo> bar(string baz)
{
    vector<foo> out;
    for (each letter in baz)
        out.push_back(someTable[letter]);

    return out;
}

int main()
{
     vector<foo> oof = bar("Hello World");
}

bar(vector & out, string text) を使用しても特に問題はありませんが、上記の方法は美的にも意図的にもはるかに見栄えがします。

4

3 に答える 3

10

たとえば、ウィキペディアの例では、コピーの省略は、返されるオブジェクトが完全に構築されると同時に返される場合にのみ発生するように思われます。

それは誤解を招きます(読んでください:間違っています)。問題は、すべてのコードパスで1つのオブジェクトのみが返されることです。つまり、潜在的な戻りオブジェクトに対して1つの構築のみが発生します。

あなたのコードは大丈夫です、どんな現代のコンパイラもコピーを削除することができます。

一方、次のコードは潜在的に問題を引き起こす可能性があります。

vector<int> foo() {
    vector<int> a;
    vector<int> b;
    // … fill both.
    bool c;
    std::cin >> c;
    if (c) return a; else return b;
}

ここで、コンパイラは2つの異なるオブジェクトを完全に構築する必要があり、どちらを返すかを後で決定するだけです。したがって、ターゲットメモリの場所に返されたオブジェクトを直接構築できないため、一度コピーする必要があります。

于 2011-05-26T13:27:29.610 に答える
5

コンパイラがコピーを削除するのを妨げるものは何もありません。これは12.8.15で定義されています。

[...]このコピー操作の省略は、次の状況で許可されます(複数のコピーを削除するために組み合わせることができます)。

[...]

  • 参照(12.2)にバインドされていない一時クラスオブジェクトが同じcv-unqualifiedタイプのクラスオブジェクトにコピーされる場合、省略されたコピーのターゲットに一時オブジェクトを直接構築することにより、コピー操作を省略できます。

それが実際に行われるかどうかは、コンパイラと使用する設定によって異なります。

于 2011-05-26T13:26:21.283 に答える
4

缶の暗黙のコピーは両方ともvector(そして多くの場合は削除されます)削除されます。名前付き戻り値の最適化により、returnステートメントに含まれるコピーを削除できます。return out;また、のコピーの初期化に含まれる一時的なoofものも削除できます。

両方の最適化を実行すると、で構築されたオブジェクトvector<foo> out;はと同じオブジェクトになりますoof

このような人工的なテストケースを使用すると、これらの最適化のどれが実行されているかをテストする方が簡単です。

struct CopyMe
{
    CopyMe();
    CopyMe(const CopyMe& x);
    CopyMe& operator=(const CopyMe& x);

    char data[1024]; // give it some bulk
};

void Mutate(CopyMe&);

CopyMe fn()
{
    CopyMe x;
    Mutate(x);
    return x;
}

int main()
{
    CopyMe y = fn();
    return 0;
}

コピーコンストラクターは宣言されていますが、定義されていないため、コピーコンストラクターへの呼び出しをインライン化して削除することはできません。現在比較的古いgcc4.4でコンパイルすると、で次のアセンブリが得られます-O3 -fno-inline(C ++名をデマングルするためにフィルター処理され、非コードを削除するために編集されます)。

fn():
        pushq   %rbx
        movq    %rdi, %rbx
        call    CopyMe::CopyMe()
        movq    %rbx, %rdi
        call    Mutate(CopyMe&)
        movq    %rbx, %rax
        popq    %rbx
        ret

main:
        subq    $1032, %rsp
        movq    %rsp, %rdi
        call    fn()
        xorl    %eax, %eax
        addq    $1032, %rsp
        ret

ご覧のとおり、コピーコンストラクターの呼び出しはありません。実際、gccはでさえこれらの最適化を実行し-O0ます。-fno-elide-constructorsこの動作をオフにするには、を指定する必要があります。これを行うと、gccはのコピーコンストラクターへの2つの呼び出しを生成します。1つはへの呼び出しのCopyMe内側と外側ですfn()

fn():
        movq    %rbx, -16(%rsp)
        movq    %rbp, -8(%rsp)
        subq    $1048, %rsp
        movq    %rdi, %rbx
        movq    %rsp, %rdi
        call    CopyMe::CopyMe()
        movq    %rsp, %rdi
        call    Mutate(CopyMe&)
        movq    %rsp, %rsi
        movq    %rbx, %rdi
        call    CopyMe::CopyMe(CopyMe const&)
        movq    %rbx, %rax
        movq    1040(%rsp), %rbp
        movq    1032(%rsp), %rbx
        addq    $1048, %rsp
        ret

main:
        pushq   %rbx
        subq    $2048, %rsp
        movq    %rsp, %rdi
        call    fn()
        leaq    1024(%rsp), %rdi
        movq    %rsp, %rsi
        call    CopyMe::CopyMe(CopyMe const&)
        xorl    %eax, %eax
        addq    $2048, %rsp
        popq    %rbx
        ret
于 2011-05-26T13:27:05.867 に答える