4

私は最近、しばらく不在だったのですが、再び C++11 を試していました。インターネットで多くの記事を読んだ後、ファクトリ関数からラージ オブジェクトを返す最も効率的な方法 (基本的には、データベース)。

私はunique_ptrのファンになりましたが、いくつかの記事で、新しいムーブコンストラクターのおかげで、大きなベクトルを値で返すことが完全に可能になり、これらの新しいセマンティクスにより、1つのポインターをコピーするのと同じくらい高速になるはずだと読みました.

これを試すために、さまざまなコンストラクターで出力を行う小さなテスト プログラムを作成しました。

#include <iostream>
#include <memory>

using namespace std;


class C {
public:
    C( string n ) : _name{n} { cout << "Constructing a C named '" << _name << "'\n"; };

    C() : _name( "EMPTY" ) { cout << "Default-constructing a C named '" << _name << "'\n"; } ;     // default-ctor

    C( const C& c ) : _name{c._name} {
        _name += " [copied]";
        cout << "Copy-constructing a C named '" << _name << "'\n";
    };

    C( C&& c )
        : _name{c._name} {
        _name += " [moved]";
        cout << "Move-constructing a C named '" << _name << "'\n";
    };

    ~C() { cout << "Destructing a C named '" << _name << "'\n"; };

    string getName() { return _name; };

private:
    string _name;
};

およびテスト済み

C fooVal() {    
    cout << "In fooVal\n";
    string str = "value return";
    C c(str);
    return c;
}

C& fooRef() {
    cout << "In fooRef\n";
    string str = "reference return";
    C* pC = new C( str );
    return *pC;
}

C* fooPtr() {
    cout << "In fooPtr\n";
    string str = "classical pointer return";
    C* pC = new C( str );
    return pC;
}

unique_ptr<C> fooUPtr() {
    cout << "In fooUPtr\n";
    string str = "unique_ptr return";
    return unique_ptr<C>(new C(str));
}

shared_ptr<C> fooSPtr() {
    cout << "In fooSPtr\n";
    string str = "shared_ptr return";
    return shared_ptr<C>(new C(str));
}

// IMPORTANT: THIS NEEDS TO BE COMPILED WITH FLAG -fno-elide-constructors
int main(int argc, const char * argv[])
{
    C cv(fooVal());        
    cout << "cv constructed\n";    
    C& cr = fooRef();        
    cout << "cr constructed\n";        
    C* pC = fooPtr();        
    cout << "*pC constructed\n";        
    unique_ptr<C> upC = fooUPtr();        
    cout << "*upC constructed\n";        
    shared_ptr<C> spC = fooSPtr();        
    cout << "*spC constructed\n";        
    cout << "Alive: " << cv.getName() << ", " << cr.getName() << ", " << pC->getName() << ", " << upC->getName() << ".\n";
}

これをそのままコンパイルすると、コンパイラはさまざまなコンストラクター呼び出しを最適化 (「省略」) し、出力を取得します。

In fooVal
Constructing a C named 'value return'
cv constructed
In fooRef
Constructing a C named 'reference return'
cr constructed
In fooPtr
Constructing a C named 'classical pointer return'
*pC constructed
In fooUPtr
Constructing a C named 'unique_ptr return'
*upC constructed
In fooSPtr
Constructing a C named 'shared_ptr return'
*spC constructed
Alive: value return, reference return, classical pointer return, unique_ptr return.
Destructing a C named 'shared_ptr return'
Destructing a C named 'unique_ptr return'
Destructing a C named 'value return'

わかりましたが、コピーがどれだけ最適化されているかがわかります。「指定された」動作が何であるかを確認するために、フラグ -fno-elide-constructors を使用してこれをコンパイルしました (Apple LLVM バージョン 4.2 (clang-425.0.28) を使用しています)。しかし、次の出力が得られます。

In fooVal
Constructing a C named 'value return'
Destructing a C named 'value return'
Move-constructing a C named ' [moved]'
Destructing a C named ''
cv constructed
In fooRef
Constructing a C named 'reference return'
cr constructed
In fooPtr
Constructing a C named 'classical pointer return'
*pC constructed
In fooUPtr
Constructing a C named 'unique_ptr return'
*upC constructed
In fooSPtr
Constructing a C named 'shared_ptr return'
*spC constructed
Alive:  [moved], reference return, classical pointer return, unique_ptr return.
Destructing a C named 'shared_ptr return'
Destructing a C named 'unique_ptr return'
Destructing a C named ' [moved]'

したがって、明らかに、値が返されるオブジェクトで怪しいことが起こっています。明らかに、これは単なる小さな問題ではありません。なぜなら、-fno-elide-constructors はセマンティクスを変更せず、関与するコンストラクターの量だけを変更すると予想していたからです。

したがって、私は尋ねます:

  1. 何が起こっている?値オブジェクトが文字列パラメータを「失う」のはなぜですか? そしてどこに?
  2. 値の戻り値に問題があるように見えますが、他の戻り値は問題なく機能します。では、なぜ人々は最近、価値を返し、「残りはシステムが処理する」ことを推奨しているのでしょうか?
  3. 大きなオブジェクトを返す良い方法は何ですか?
  4. 見えないところでどこか間違っていますか?

ありがとう!

4

3 に答える 3

3

それはこのclangバグのようです: http://llvm.org/bugs/show_bug.cgi?id=12208これは簡略化されていますが、文字列連結に関連しており、明らかにまだ修正されていません。

于 2013-08-19T18:06:09.283 に答える
1

正直なところ、 -fno-elide-constructors が有効なプログラムになるとは思いません。

私のシステムではすぐにクラッシュするため、valgrind はすぐに重大なエラーを指摘しました。

==6098== Memcheck, a memory error detector
==6098== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==6098== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==6098== Command: ./test
==6098== 
In fooVal
Constructing a C named 'value return'
Destructing a C named 'value return'
==6098== Use of uninitialised value of size 8
==6098==    at 0x4EEF83B: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.17)
==6098==    by 0x40297C: C::C(C&&) (test.cpp:19)
==6098==    by 0x401C0C: C::C(C&&) (test.cpp:22)
==6098==    by 0x40165D: main (test.cpp:69)
==6098== 
==6098== Conditional jump or move depends on uninitialised value(s)
==6098==    at 0x4EEF84D: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.17)
==6098==    by 0x40297C: C::C(C&&) (test.cpp:19)
==6098==    by 0x401C0C: C::C(C&&) (test.cpp:22)
==6098==    by 0x40165D: main (test.cpp:69)

これは使用しています

Ubuntu clang version 3.2-9 (tags/RELEASE_32/final) (based on LLVM 3.2)
Target: x86_64-pc-linux-gnu
Thread model: posix

これはコンパイラのバグである可能性があり、-fno-elide-constructors. 私はチェックしていません。

于 2013-08-19T17:05:41.897 に答える