web-dev-qa-db-ja.com

make_sharedは本当に新しいものより効率的ですか?

私はC++ 11のshared_ptrmake_sharedを試し、make_sharedを呼び出したときに実際に何が起こっているかを見るために小さなおもちゃの例をプログラミングしました。インフラストラクチャとして、XCode4内でllvm std c ++ライブラリとともにllvm/clang 3.0を使用していました。

class Object
{
public:
    Object(const string& str)
    {
        cout << "Constructor " << str << endl;
    }

    Object()
    {
        cout << "Default constructor" << endl;

    }

    ~Object()
    {
        cout << "Destructor" << endl;
    }

    Object(const Object& rhs)
    {
        cout << "Copy constructor..." << endl;
    }
};

void make_shared_example()
{
    cout << "Create smart_ptr using make_shared..." << endl;
    auto ptr_res1 = make_shared<Object>("make_shared");
    cout << "Create smart_ptr using make_shared: done." << endl;

    cout << "Create smart_ptr using new..." << endl;
    shared_ptr<Object> ptr_res2(new Object("new"));
    cout << "Create smart_ptr using new: done." << endl;
}

出力を見てください:

Make_sharedを使用してsmart_ptrを作成...

コンストラクターmake_shared

コンストラクタをコピー...

コンストラクタをコピー...

デストラクタ

デストラクタ

Make_shared:doneを使用してsmart_ptrを作成します。

新規を使用してsmart_ptrを作成...

コンストラクターnew

New:doneを使用してsmart_ptrを作成します。

デストラクタ

デストラクタ

make_sharedはコピーコンストラクターを2回呼び出しているようです。通常のObjectを使用してnewにメモリを割り当てると、これは起こりません。Objectは1つだけ構築されます。

私が疑問に思っているのは次のことです。 make_sharednewを使用するよりも効率的であると思われます12。理由の1つは、make_sharedが同じメモリブロックで管理されるオブジェクトとともに参照カウントを割り当てるためです。わかりました、私はポイントを得ました。もちろん、これは2つの個別の割り当て操作よりも効率的です。

それどころか、これがObjectのコピーコンストラクターへの2つの呼び出しのコストを伴う理由を理解していません。このため、everyの場合にnewを使用した割り当てよりもmake_sharedの方が効率的であるとは確信していません。私はここで間違っていますか? Objectの移動コンストラクターを実装することもできますが、Objectを介してnewを単に割り当てるよりも効率的かどうかはわかりません。少なくともすべての場合でそうではありません。 Objectをコピーする方が、参照カウンターにメモリを割り当てるよりも安価であれば、それは真実です。しかし、shared_ptr-内部参照カウンタは、いくつかのプリミティブデータ型を使用して実装できますか?

コピーのオーバーヘッドが概説されているにもかかわらず、なぜmake_sharedが効率の観点から進むべき方法であるかを説明できますか?

50
user1212354

インフラストラクチャとして、XCode4内でllvm std c ++ライブラリとともにllvm/clang 3.0を使用していました。

それはあなたの問題のようです。 C++ 11標準では、make_shared<T>(およびallocate_shared<T>)、セクション20.7.2.2.6:

必要条件:式:: new(pv)T(std :: forward(args)...)(pvはvoid *型を持ち、T型のオブジェクトを保持するのに適したストレージを指します)は整形式でなければなりません。 Aはアロケーター(17.6.3.5)でなければなりません。 Aのコピーコンストラクタおよびデストラクタは、例外をスローしてはなりません。

Tnotコピー構築可能である必要があります。確かに、Tは、配置が新しく構築可能である必要さえありません。インプレースで構築可能であることが必要です。これは、make_shared<T>Tでできますnewはその場です。

そのため、得られる結果は標準と一致しません。この点で、LLVMのlibc ++は壊れています。バグレポートを提出してください。

参考のために、コードをVC2010に組み込んだときに何が起こったのかを次に示します。

Create smart_ptr using make_shared...
Constructor make_shared
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor
Destructor

また、Boostの元のshared_ptrおよびmake_shared、そして私はVC2010と同じものを得た。

Libc ++の動作が壊れているため、バグレポートを提出することをお勧めします。

37
Nicol Bolas

これら2つのバージョンを比較する必要があります。

std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

コードでは、2番目の変数は単なる共有ポインターではなく、単なるネイキッドポインターです。


肉の上に。 make_sharedis(実際には)より効率的です。これは、参照制御ブロックを実際のオブジェクトとともに1つの動的割り当てで割り当てるためです。対照的に、ネイキッドオブジェクトポインターをとるshared_ptrのコンストラクターは、参照カウントにanother動的変数を割り当てる必要があります。トレードオフは、割り当てがアロケータによって実行されるため、make_shared(またはそのいとこallocate_shared)ではカスタム削除機能を指定できないことです。

(これは、オブジェクト自体の構築には影響しません。Objectの観点からは、2つのバージョンに違いはありません。より効率的なのは、管理オブジェクトではなく共有ポインタ自体です。)

32
Kerrek SB

念頭に置いておくべきことの1つは、最適化設定です。特にc ++に関するパフォーマンスの測定は、meaningless最適化が有効になっていない場合です。実際に最適化を使用してコンパイルしたかどうかはわかりませんので、言及する価値があると思いました。

そうは言っても、このテストで測定しているのはnotであり、_make_shared_がより効率的です。簡単に言えば、あなたは間違ったものを測定しています:-P。

これが取引です。通常、共有ポインターを作成すると、少なくとも2つのデータメンバー(おそらくそれ以上)が含まれます。 1つはポインター用で、もう1つは参照カウント用です。この参照カウントはヒープに割り当てられます(したがって、異なるライフタイムで_shared_ptr_で共有できるように...それが結局のところポイントです!)

したがって、std::shared_ptr<Object> p2(new Object("foo"));のようなオブジェクトを作成している場合、少なくとも2newの呼び出しがあります。 1つはObject用で、もう1つは参照カウントオブジェクト用です。

_make_shared_には、同じ連続ブロック内でポイントされたオブジェクトと参照カウントを保持するのに十分な大きさの単一のnewを実行するオプションがあります(そうする必要はありません)。このように見えるオブジェクトを効果的に割り当てます(文字通りではなく、例証です)。

_struct T {
    int reference_count;
    Object object;
};
_

参照カウントとオブジェクトの存続期間は結び付けられているため(一方が他方より長く生きることは意味がありません)。このブロック全体を同時にdeletedにすることもできます。

そのため、効率はコピーではなく割り当てにあります(他の何よりも最適化に関係していると思われます)。

明確にするために、これは_make_shared_についてブーストが言っていることです

http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/make_shared.html

便利さとスタイルに加えて、このような関数は、オブジェクトとそれに対応する制御ブロックの両方に単一の割り当てを使用でき、shared_ptrの構築オーバーヘッドのかなりの部分を排除できるため、例外安全でかなり高速です。これにより、shared_ptrに関する効率の大きな不満の1つがなくなります。

6
Evan Teran

あなたはそこに余分なコピーを取得すべきではありません。出力は次のようになります。

Create smart_ptr using make_shared...
Constructor make_shared
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor

なぜ余分なコピーを入手しているのかわかりません。 (1つの「デストラクタ」を取得しすぎているので、出力を取得するために使用したコードは、投稿したコードとは異なる必要があります)

make_sharedは、2つではなく1つの動的割り当てのみを使用して実装でき、共有オブジェクトごとに1つのポインタのメモリを必要としないため、より効率的です。

編集:Xcode 4.2ではチェックしませんでしたが、Xcode 4.3では、質問に示された不正な出力ではなく、上記の正しい出力が得られます。

3
bames53