54

私はC++11を試したり、C ++ 11から実験しshared_ptrたりmake_sharedして、小さなおもちゃの例をプログラムして、を呼び出したときに実際に何が起こっているかを確認しましたmake_shared。インフラストラクチャとして、XCode4内のllvm stdc++ライブラリとともにllvm/clang3.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を使用してsmart_ptrを作成します:done。

new...を使用してsmart_ptrを作成します。

新しいコンストラクタ

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

デストラクタ

デストラクタ

make_sharedコピーコンストラクタを2回呼び出しているようです。Objectレギュラーを使用してメモリを割り当てる場合、newこれは発生せず、1つだけObjectが構築されます。

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

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

make_shared概説されたコピーのオーバーヘッドにもかかわらず、なぜ効率の観点から進むべきかを助け、説明できますか?

4

4 に答える 4

41

インフラストラクチャとして、XCode4内のllvm stdc++ライブラリとともにllvm/clang3.0を使用していました。

それがあなたの問題のようです。C ++ 11標準では、セクション20.7.2.2.6で、make_shared<T>(および)の次の要件が規定されています。allocate_shared<T>

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

Tコピー構築可能である必要はありません。確かに、T非配置である必要はありません-新しく構築可能です。インプレースで構築可能である必要があるだけです。これは、それでmake_shared<T>できる唯一のことTnewそれがインプレースであることを意味します。

したがって、得られる結果は標準と一致していません。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_ptrmake_sharedに移植し、VC2010と同じものを入手しました。

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

于 2012-02-15T23:54:04.990 に答える
33

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

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

コードでは、2番目の変数は単なる裸のポインターであり、共有ポインターではありません。


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

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

于 2012-02-15T22:17:22.900 に答える
6

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

とはいえ、このテストで測定しているのは、より効率的な方法ではありません。make_shared簡単に言えば、あなたは間違ったことを測定しています:-P。

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

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

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

struct T {
    int reference_count;
    Object object;
};

参照カウントとオブジェクトの存続期間は相互に関連しているため(一方が他方より長く存続することは意味がありません)。このブロック全体をdelete同時にdにすることもできます。

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

明確にするために、これはブーストが約について言わなければならないことですmake_shared

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

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

于 2012-02-15T22:25:11.960 に答える
3

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

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_shared2つではなく1つの動的割り当てのみを使用して実装でき、共有オブジェクトごとのブックキーピングが少ない1ポインター分のメモリが必要なため、より効率的です。

編集:Xcode 4.2では確認しませんでしたが、Xcode 4.3では、質問に示されている誤った出力ではなく、上記の正しい出力が得られます。

于 2012-02-15T22:25:08.967 に答える