2012-06-08 8 views
13

考慮する: - 一つは実際移動shared_ptr、および他だけコピーshared_ptrstd::moveを使用する上記のコードで私はstd :: move_ptrを移動コンストラクタで移動する必要がありますか?

#include <cstdlib> 
#include <memory> 
#include <string> 
#include <vector> 
#include <algorithm> 
#include <iterator> 
using namespace std; 

class Gizmo 
{ 
public: 
    Gizmo() : foo_(shared_ptr<string>(new string("bar"))) {}; 
    Gizmo(Gizmo&& rhs); // Implemented Below 

private: 
    shared_ptr<string> foo_; 
}; 

/* 
// doesn't use std::move 
Gizmo::Gizmo(Gizmo&& rhs) 
: foo_(rhs.foo_) 
{ 
} 
*/ 


// Does use std::move 
Gizmo::Gizmo(Gizmo&& rhs) 
: foo_(std::move(rhs.foo_)) 
{ 
} 

int main() 
{ 
    typedef vector<Gizmo> Gizmos; 
    Gizmos gizmos; 
    generate_n(back_inserter(gizmos), 10000, []() -> Gizmo 
    { 
     Gizmo ret; 
     return ret; 
    }); 

    random_shuffle(gizmos.begin(), gizmos.end()); 

} 

Gizmo::Gizmo(Gizmo&&)の2つのバージョンがあります。

両方のバージョンが表面上で動作するようです。 1つの違い(私が見ることができる唯一の違い)はmoveバージョンであり、shared_ptrの参照カウントは一時的に増加しますが、一時的に増加します。

私は通常moveshared_ptrに進みますが、私のコードではっきりしていて一貫性があります。私はここで考慮を欠いていますか? テクニカルの理由で、1つのバージョンを他のバージョンよりも優先したいですか?

+2

移動コンストラクタの移動は、少なくとも意味的に一貫しています... – ildjarn

+1

なぜ文字列をshared_ptrに保持しますか?メンバ変数としてのshared_ptrは、しばしば設計不良の兆候です。 –

+1

移動コンストラクタでの移動は、コンパイラが自動的に生成するものと同じです。 –

答えて

16

ここでの主な問題は、shared_ptrの余分なアトミック・インクリメントとデクリメントによるパフォーマンスの小さな違いではありませんが、移動を実行しない限り、操作のセマンティクスは矛盾しています。

shared_ptrの参照カウントは一時的なものに過ぎないと仮定していますが、そのような保証はありません。移動元のソースオブジェクトは一時的なものになる可能性がありますが、存続期間が非常に長くなる可能性もあります。それはないshared_ptrからを移動することで、その場合、あなたはまだ、移動のソースと所有権を共有し維持し、もしされている、(std::move(var)を言う)右辺値参照にキャストされた名前の変数可能性があり宛先shared_ptrのスコープが小さければ、指示されたオブジェクトの有効期間が不必要に延長されます。

+0

私は動きを使うとき、「この動きはコピーに変わるかもしれない」と私たちは考えていますか?明らかに、我々は実際に移動コンストラクタを必要としないので、任意の 'MoveConstructible'型' T'のテンプレートコードを書くときに行います。ソースを変更するものはもちろんですが、また、移動元のオブジェクトが不必要にリソースを保持している場合、QoIが不十分であることも明らかです.Gizmoに移動コンストラクタがある場合は、それが良いものになるはずです。しかし、私はそれがコンベンションとコーディングスタイルの問題であると考えています。 –

+0

+1、これはジェームズの答えに対する私のコメントで私が得たものです。よく置きます。 – ildjarn

+0

別の例として、移動割り当ての有効な実装は 'swap'を呼び出すことです。移動元オブジェクトの状態は指定されていないため、特に移動先オブジェクトの状態にすることができます。しかし、*移動先のオブジェクトのリソースが無期限にハングアップしていると少し気になります。 –

11

moveの使用が好ましい:それは参照カウントの余分な原子増分および減分を必要としないので、コピーよりも効率的でなければならない。

+0

意味の違いもあります:移動した「Gizmo」から内部共有状態を生かし続けることを期待しますか?個人的に私は驚くべきことに気づくでしょう、そして、move-fromオブジェクトがすべての共有状態を解放することを期待します。 – ildjarn

+1

@ildjarn:プログラムの正しさには何の影響もありませんが、私は同意します。オブジェクトから移動されたものは、依然として破壊可能で割り当て可能です。 –

13

私はJames McNellisの答えをアップしました。私は彼の答えについてコメントしたいと思いますが、私のコメントはコメント形式に収まりません。だから私はここに入れている。

shared_ptrのコピーと1つのコピーのパフォーマンスの影響を測定する楽しい方法は、vector<shared_ptr<T>>のようなものを使用して、それらの全体を移動またはコピーして時間を計ることです。ほとんどのコンパイラでは、言語モード(-std = C++ 03や-std = C++ 11など)を指定することによって、移動セマンティクスをオン/オフする方法があります。ここで

は、私はちょうど-O3でテストコードです:

#include <chrono> 
#include <memory> 
#include <vector> 
#include <iostream> 

int main() 
{ 
    std::vector<std::shared_ptr<int> > v(10000, std::shared_ptr<int>(new int(3))); 
    typedef std::chrono::high_resolution_clock Clock; 
    typedef Clock::time_point time_point; 
    typedef std::chrono::duration<double, std::micro> us; 
    time_point t0 = Clock::now(); 
    v.erase(v.begin()); 
    time_point t1 = Clock::now(); 
    std::cout << us(t1-t0).count() << "\u00B5s\n"; 
} 

打ち鳴らすの使用/ libcの++と-std = C++ 03で、これは私のために出力します。

への切り替え
195.368µs 

- std = C++ 11私は得る:

16.422µs 

あなたの走行距離は異なる場合があります。

+2

+1:ただの観測。上記の 'Gizmo'クラスを使用するようにこれを変更したとき、' move'と 'move'のバージョンはほぼ同じでした。これは、MSVC10上で、 'std :: chrono'ではなくboost :: chronoを使用し、x64リリースをコンパイルしました。 –

+0

@ JohnDibling:興味深い。なぜ私たちの結果にそのような格差があるのか​​を理解したら、それについて聞いてみたいと思います。試みるべきこと:あなたの移動コンストラクタにnoexceptを入れてください。 MSVC10がこれを実装するかどうかはわかりません。 noexceptがどれほど遅れて来たかを考えれば驚くだろう。そして、実際にはこれがvector :: eraseメンバのために違いを生むとは思っていません。しかしそれにもかかわらず、それは私が試みる最初のものです。私は2.8 GHzのIntel Core i5(64ビット用にコンパイルされています)を実行しています。数百マイクロ秒、または数十マイクロ秒のオーダで結果を得ましたか? –

関連する問題