c++ - operator - std move destructor




C++ 0x rvalue参照-lvalues-rvalueバインディング (2)

このコードはどうですか?

void f(std::string &&); //NB: No const string & overload supplied

void g2(const std::string & arg)
{
    f(arg);
}

...しかし、GCCとMSVCは、lvalueがrvalue ref引数にバインドすることを禁止する13.3.3.1.4 / 3節のためにg2を拒否します。 私はこれの背後にある論理的根拠を理解しています - それはN2831「rvalue参照による安全問題の解決」で説明されています。 私はまた、GCCへの元のパッチが著者のひとり(Doug Gregor)によって書かれたので、GCCがおそらくこの論文の著者が意図した通りにこの節を実装していると思います。

いいえ、それは両方のコンパイラがあなたのコードを拒否する理由の半分に過ぎません。 もう1つの理由は、constオブジェクトを参照する式でnon-constへの参照を初期化できないということです。 だから、N2831以前でさえ、これはうまくいきませんでした。 文字列はすでに文字列であるため、変換は必要ありません。 string&&ようなstringを使いたいと思うようです。 次に、関数f書くだけで、文字列を値で取るようにします。 コンパイラがconst文字列lvalueの一時的なコピーを作成して、 string&&を取る関数を呼び出すことができるようにするには、文字列を値またはrrefで取ることの違いはありませんか?

N2831はこのシナリオとはほとんど関係がありません。

移動可能な引数をいくつか受け取り、できる場合はそれらを移動する関数(例えば、ファクトリ関数/コンストラクタ:Object create_object(文字列、ベクトル、文字列)など)を持ち、移動またはコピーしたいそれぞれの引数が適切であれば、すぐにたくさんのコードを書くことになります。

あんまり。 なぜたくさんのコードを書いていますか? const& && overloadを使用してすべてのコードをconst&理由はほとんどありません。 パラメータで何をしたいのかに応じて、値渡しと参照渡しを組み合わせた単一の関数を引き続き使用することができます。 工場に関しては、アイデアは完璧な転送を使用することです:

template<class T, class... Args>
unique_ptr<T> make_unique(Args&&... args)
{
    T* ptr = new T(std::forward<Args>(args)...);
    return unique_ptr<T>(ptr);
}

...そしてすべてが順調です。 特殊なテンプレート引数の控除ルールは、lvalueとrvalueの引数を区別するのに役立ち、std :: forwardは、実際の引数と同じ「価値」を持つ式を作成することを可能にします。 だから、あなたが次のようなことを書くなら、

string foo();

int main() {
   auto ups = make_unique<string>(foo());
}

fooが返す文字列は自動的にヒープに移動されます。

したがって、lvaluesが暗黙的なコピーを介して右辺値にバインドした場合、create_object(legacy_string &&、legacy_vector &&、legacy_string &&)のような過負荷を書くことができ、rvalue / lvalue参照オーバーロードのすべての組み合わせを提供するようになります。 ..

さて、値でパラメータを取る関数とほぼ同等です。 冗談なし。

これは、GCCのための実験的なパッチなど、作る価値のある十分な改善ですか?

改善はありません。

これは、 C ++ 0x rvalueの参照と一時的なものの後続の質問です

前の質問では、私はこのコードがどのように機能すべきか尋ねました:

void f(const std::string &); //less efficient
void f(std::string &&); //more efficient

void g(const char * arg)
{
    f(arg);
}

一時的な暗黙のために移動オーバーロードが呼び出されているようですが、MSVC(またはMSVCのIntellisenseで使用されるEDGフロントエンド)ではなくGCCで発生します。

このコードはどうですか?

void f(std::string &&); //NB: No const string & overload supplied

void g1(const char * arg)
{
     f(arg);
}
void g2(const std::string & arg)
{
    f(arg);
}

私の以前の質問に対する答えに基づいて、関数g1は合法である(そして、MSVCではなく、GCC 4.3-4.5によって受け入れられる)と思われる。 しかし、GCCとMSVCは、lvalueがrvalue ref引数にバインドすることを禁止する項13.3.3.1.4 / 3のためにg2拒否します。 私はこれの背後にある論理的根拠を理解しています - それはN2831「rvalue参照による安全問題の解決」で説明されています。 私はまた、GCCの元のパッチが著者の一人(Doug Gregor)によって書かれたので、GCCがおそらくこの論文の著者が意図した通りにこの節を実装していると思います。

しかし、私はこれは直感的ではありません。 私にとって、(a) const string &は概念的にconst char *よりもstring &&近く、(b)コンパイラはg2に一時文字列を作成することができます。

void g2(const std::string & arg)
{
    f(std::string(arg));
}

確かに、コピーコンストラクタは暗黙の変換演算子と見なされることがあります。 構文的には、これはコピーコンストラクタの形式で示唆されており、標準では、派生ベース変換のコピーコンストラクタに他のユーザ定義の変換ランクよりも高い変換ランクが与えられている13.3.3.1.2 / 4節で特に言及していますコンバージョン:

クラス型の式の同じクラス型への変換には完全一致のランクが与えられ、その型の基本クラスへのクラス型の式の変換ランクが与えられるが、コピー/移動そのような場合にコンストラクタ(つまり、ユーザ定義の変換関数)が呼び出されます。

(これは、派生クラスを値によって基本クラスを取るvoid h(Base)ような関数に渡すときに使用されると仮定しています)。

動機

これを求める私の動機は、新しいc ++ 0x rvalue参照演算子のオーバーロードを追加するときの冗長コードの削減方法(「新し​​いC ++ 0x r値参照演算子のオーバーロードを追加するときの冗長コードの削減方法」)

多くの潜在的に可動な引数を受け取り、可能ならばそれらを移動する関数(例えば、ファクトリ関数/コンストラクタ: Object create_object(string, vector<string>, string)など)を持っていて、それぞれの引数を適切に移動またはコピーすると、すばやく多くのコードを書くようになります。

引数の型が移動可能な場合、上記のように引数を値で受け入れるバージョンを書くことができます。 しかし、その引数が、C ++ 03の(移動可能ではなくスワップ不可能な)クラスであり、それらを変更できない場合、rvalue参照のオーバーロードを書く方が効率的です。

したがって、lvaluesが暗黙的なコピーを介して右辺値にバインドした場合、 create_object(legacy_string &&, legacy_vector<legacy_string> &&, legacy_string &&)ような過負荷を書くことができcreate_object(legacy_string &&, legacy_vector<legacy_string> &&, legacy_string &&) / lvalue参照過負荷 - 左辺値だった実際の引数はコピーされて引数にバインドされ、右辺値であった実際の引数は直接バインドされます。

明確化/編集:これは、C ++ 0x std :: stringやstd :: vector(moveコンストラクタが概念的に呼び出された回数だけ保存する)のような、可動型の値を引数として受け入れることと事実上同じです。 ただし、明示的に定義されたコピーコンストラクタを持つすべてのC ++ 03クラスを含む、コピー可能ではあるが非可動のタイプの場合は同じではありません。 この例を考えてみましょう。

class legacy_string { legacy_string(const legacy_string &); }; //defined in a header somewhere; not modifiable.

void f(legacy_string s1, legacy_string s2); //A *new* (C++0x) function that wants to move from its arguments where possible, and avoid copying
void g() //A C++0x function as well
{
    legacy_string x(/*initialization*/);
    legacy_string y(/*initialization*/);

    f(std::move(x), std::move(y));
}

gf呼び出すと、 xyがコピーされます。コンパイラがそれらをどのように動かせるかはわかりません。 flegacy_string &&引数を取ると代わりに宣言された場合、呼び出し側が明示的に引数に対してstd::moveを呼び出したコピーを避けることができます。 私はこれらがどのように同等であるかはわかりません。

質問

私の質問は次のとおりです:

  1. これは標準の有効な解釈ですか? いずれにしても、それは従来のものでも意図されたものでもないようです。
  2. それは直感的な意味を持っていますか?
  3. 私は見ていないというアイデアに問題はありますか?まったく期待されていないコピーを静かに作成できるようですが、これはC ++ 03の現場での現状です。現時点では有効ではありませんが、実際には問題ではありません。
  4. これは、GCCのための実験的なパッチなど、作る価値のある十分な改善ですか?

私はこの質問であなたのポイントをよく見ていない。 移動可能なクラスをお持ちの場合は、 Tバージョンが必要です。

struct A {
  T t;
  A(T t):t(move(t)) { }
};

クラスは伝統的ですが効率的なswap場合は、スワップバージョンを書くことができます。あるいは、 const T& wayにフォールバックできます

struct A {
  T t;
  A(T t) { swap(this->t, t); }
};

スワップ版に関して、私はむしろそのスワップの代わりにconst T& wayで行くだろう。 スワップ技術の主な利点は、例外的な安全性であり、呼び出し元にコピーを近づけて一時的なコピーを最適化できるようにすることです。 とにかくオブジェクトを作成しているだけなら、何を保存する必要がありますか? コンストラクタが小さい場合、コンパイラはそれを調べ、コピーを最適化することもできます。

struct A {
  T t;
  A(T const& t):t(t) { }
};

私にとっては、文字列の左辺値を右辺値の参照にバインドするために自動的に右辺値のコピーに変換するのは正しいとは思われません。 r値参照はrvalueにバインドされていることを示します。 しかし、同じ型の左辺値にバインドしようとすると、よりうまく失敗します。 人々がX&&を見てX左辺値を渡すと、ほとんどの場合、コピーは存在しないと予想され、バインディングはまったく問題ないと思うので、隠されたコピーを導入することで、私にとって正しいとは言えません。 ユーザーが自分のコードを修正できるようにすぐに失敗する方が良い。





lvalue