2016-08-28 12 views
1

予期せぬ(少なくとも私にとっては)動作が見つかりました。私はコード内のどこか後std :: moveは変数のアドレスを変更しますか?

class A 
{ 
    char _text[100]; 
    char* _beg; 
    char* _end; 
public: 
    explicit A(char* text, size_t tsize) : _beg(&text[0]), _end(&text[std::min(tsize, 99)]) 
    { 
     memcpy(_text, text, std::min(tsize, 99)); 
     *_end = '\0'; 
    } 
    inline std::string get_text() 
    { 
     return std::move(std::string(_beg, _end)); 
    } 
}; 

こと:

A* add_A(A&& a) 
    { 
    list_a.push_back(std::move(a)); 
    return &(list_a.back()); 
    } 

    std::list<A> list_a; 
    { 
     add_A(A("my_text", 7)); 
     list_a.back().get_text(); //returns "my_text" 
    } 
    list_a.back().get_text(); //returns trash 

として唯一私が後にいるかのように、ゴミを取得し、(std::moveを使用して)このクラスを移動し、移動されたオブジェクトのget_text()を呼び出します変数_textの移動アドレスが変更されたため、_beg_endはどこにもありません。

std::moveの後に変数のアドレスが実際に変更されることがありますか(私はmoveが実際にオブジェクトを移動しないと思っていましたが、そのために発明されました)?

変更可能な場合は、それを処理する通常のパターンは何ですか(それに応じてポインタを変更する)?

変更できない場合は、そのオブジェクトをstd::listに移動しようとするとその動作が発生する可能性があります(何らかの理由でコピーが発生すると、変数のアドレスが変更され、ポインタが間違った位置を指してしまいます)。

+0

あなたのコードは 'std :: move'とは関係ありません。それはあなたがそれを使用しない場合でも同じ問題を抱えます。 – milleniumbug

+3

'_end = '\ 0';' '* _end'を意味しましたか? – Michael

+0

@Michael、はい、そうです、ここでコードをコピーすると間違いが起こります – Arkady

答えて

2

std::moveは、それが単に右辺値参照に入力パラメータを促進する可動部を有していません。

inline std::string get_text() 
{ 
    return std::move(std::string(_beg, _end)); 
} 

これを破壊:

std::string(_beg, _end); 

_end_begから構築匿名の、一時的なのstd ::文字列オブジェクトを作成します。これは値です。

std::move(...); 

は強制的にこれをrvalue参照に昇格させ、コンパイラが戻り値の最適化を実行するのを防ぎます。何が欲しいのは

return std::string(_beg, _end); 

はおそらくまた残念ながら、このアプローチに2つの欠陥がある

list_a.emplace_back(std::move(a)); 

を使用したいassembly code comparison

を参照してくださいています。

より簡単に言えば、movingは少し誤解を招く可能性がありますが、それは非常に一方的に聞こえます。このライン

struct S { 
    char* s_; 
    S(const char* s) : s_(strdup(s)) {} 
    ~S() { release(); } 
    void release() { if (s_) free(s_); } 
    S(const S& s) : s_(strdup(s.s_)) {} 
    S(S&& s) : s_(s.s_) { s.s_ = nullptr; } 
    S& operator=(const S& s) { release(); s_ = strdup(s); return *this; } 
    S& operator=(S&& s) { std::swap(s_, s.s_); return *this; } 
}; 

注:

一時オブジェクトがスコープの外に出たとき、それは以前に所有しているものは何でも他のオブジェクトのクリーンアップを実行するように二つのオブジェクト交換特性:しかし、実際には、多くの場合、スワップ2つの方法であります
S& operator=(S&& s) { std::swap(s_, s.s_); return *this; } 

我々は書く:

S s1("hello"); 
s1 = S("world"); 

二行目は、移動-代入演算子を呼び出します。 helloのコピーのポインタが一時的に移動し、一時的な範囲外になり破棄され、 "hello"のコピーが解放されます。文字のあなたの配列で、このスワップを行う

は一方向のコピーは場合よりもはるかに効率が低い:

struct S { 
    char s_[100]; 
    S(const S& s) { 
     std::copy(std::begin(s.s_), std::end(s.s_), std::begin(s_)); 
    } 
    S(S&& s) { 
     char t_[100]; 
     std::copy(std::begin(s.s_), std::end(s.s_), std::begin(t_)); 
     std::copy(std::begin(s_), std::end(s_), std::begin(s.s_)); 
     std::copy(std::begin(t_), std::end(t_), std::end(s_)); 
    } 
}; 

あなたはこれを行うにを持っていない、右辺値パラメータは唯一である必要があります状態を破壊するために安全ですが、上記はのデフォルトの移動オペレータが行う予定です。

あなたのコードの厄介な部分は、デフォルトの移動演算子がナイーブであることです。

struct S { 
    char text_[100]; 
    char *beg_, *end_; 
    S() : beg_(text_), end_(text_ + 100) {} 
}; 

は、次のコピーの建設を検討してください。

S s(S()); 

s.beg_ポイントを何?

回答:S().text_であり、s.text_ではありません。 text_の内容をコピーし、元の値をコピーするのではなく、beg_end_を自身のtext_に指定したコピーコンストラクタを作成する必要があります。

同じ問題は、移動演算子で発生します。それはtext_内容を移動しますが、それはまた移動ポインタ、彼らは相対的であることを見当もつかないでしょう。

コピー/移動コンストラクタや代入演算子を記述するか、beg_end_を単一のsize_tサイズの値に置き換えることが考えられます。

どちらの場合でも、所有権を移譲していないか、または浅いコピーを実行していない場合は、すべてのデータがオブジェクト内にあります。

3

C++での移動は、移動するオブジェクト内のデータを変更する専用の形式です。それはどのようにunique_ptrの作品です。 1つのunique_ptrオブジェクトから別のオブジェクトへポインタをコピーし、元の値をNULLに設定します。

オブジェクトを移動すると、という新しいオブジェクトが作成され、別のオブジェクトからデータが取得されます。メンバーの住所は「変更」されません。単にと同じオブジェクトではないです。

コピー/移動コンストラクタを記述していないので、コンパイラはコピー/ムーブコンストラクタを記述していないため、そして彼らがするのは、それぞれの要素をコピーすることだけです。したがって、新しく移動されたオブジェクトには、を指すポインタがあります。

破壊しようとしているオブジェクト。

これはあなたの古いものと同じに見えることが起こる家に移動するようなものです。どんなに古い家のように見えても、そうではありません。新しい家であるため、住所を変更する必要があります。 _beg_endのアドレスも更新する必要があります。

ここでポインタを更新するために、移動コンストラクタ/代入演算子(コピーコンストラクタ/代入演算子とともに)を作成できます。しかし、かなり率直に言って、それはちょうど悪いデザイン以上の壁紙です。あなたがそれを助けることができるなら、同じオブジェクト内のサブオブジェクトへのポインタを持つことは良い考えではありません。代わりに、開始/終了ポインタの、ちょうど実際のサイズあります

class A 
{ 
    char _text[100]; 
    size_t _size; 
public: 
    explicit A(char* text, size_t tsize) : _size(tsize) 
    { 
     strncpy(_text, text, 100); 
    } 
    inline std::string get_text() 
    { 
     return std::string(_text, _size); //Explicit `move` call is unnecessary 
    } 
}; 

をこのように、開始/終了ポインタを格納する必要はありません。これらは必要に応じて合成することができます。 foo(T&& t) { ... }の体内名前によってtの使用は、左辺値(右辺値を参照)として評価することを覚えておく -

+0

しかし、2回目の呼び出しでゴミが返されるのはなぜですか?私が知る限り、メンバ変数は変更されませんでした。 – Rakete1111

+3

'_end'と' _beg'は依然として元のオブジェクトをポイントしていますが、そのオブジェクトは割り当てが解除されています。 – qxz

関連する問題