16

これは、 C++0x右辺値参照と一時的なものに対する後続の質問 です。

前の質問で、このコードがどのように機能するかを尋ねました。

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

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

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

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

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は合法であるようです(GCC 4.3-4.5では受け入れられますが、MSVCでは受け入れられません)。g2ただし、 13.3.3.1.4 / 3節では、左辺値が右辺値のref引数にバインドできないため、GCCとMSVCはどちらも拒否します。私はこの背後にある理論的根拠を理解しています-それはN2831「右辺値参照による安全性の問題の修正」で説明されています。また、GCCの元のパッチは著者の1人(Doug Gregor)によって書かれたため、GCCはおそらくその論文の著者が意図したとおりにこの条項を実装していると思います。

しかし、これは非常に直感的ではありません。私にとって、(a)aconst string &は概念的にastring &&よりもaconst char *に近く、(b)コンパイラは、次のg2ように記述されているかのように、に一時的な文字列を作成できます。

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

実際、コピーコンストラクターは暗黙の変換演算子と見なされる場合があります。構文的には、これはコピーコンストラクターの形式で提案され、標準では13.3.3.1.2/4節で具体的に言及されています。ここでは、派生ベース変換のコピーコンストラクターに他のユーザー定義よりも高い変換ランクが与えられます。変換:

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

void h(Base)(これは、派生クラスを、基本クラスを値で受け取るのような関数に渡すときに使用されると思います。)

動機

これを尋ねる私の動機は、新しいc ++ 0x右辺値参照演算子のオーバーロードを追加するときに冗長コードを減らす方法(「新し​​いc ++ 0x右辺値参照演算子のオーバーロードを追加するときに冗長コードを減らす方法」)で尋ねられた質問のようなものです。

移動可能な可能性のある多数の引数を受け入れる関数があり、可能な場合はそれらを移動し(たとえば、ファクトリ関数/コンストラクターObject create_object(string, vector<string>, string)など)、必要に応じて各引数を移動またはコピーする場合は、すぐに記述を開始します。たくさんのコード。

引数の型が移動可能である場合は、上記のように、値で引数を受け入れる1つのバージョンを記述できます。ただし、引数がC ++ 03の(レガシー)移動不可であるが交換可能なクラスであり、それらを変更できない場合は、右辺値参照のオーバーロードを作成する方が効率的です。

したがって、左辺値が暗黙的なコピーを介して右辺値にバインドされた場合、次のようなオーバーロードを1つだけ記述することができcreate_object(legacy_string &&, legacy_vector<legacy_string> &&, legacy_string &&)、右辺値と左辺値の参照オーバーロードのすべての組み合わせを提供するように機能します-左辺値であった実際の引数はコピーされてからバインドされます引数には、右辺値である実際の引数が直接バインドされます。

明確化/編集:これは、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));
}

gを呼び出すfと、xとがコピーされyます-コンパイラがそれらを移動する方法がわかりません。f代わりに引数を取ると宣言された場合、呼び出し元が引数に対してlegacy_string &&明示的に呼び出したコピーを回避できstd::moveます。これらがどのように同等であるかわかりません。

質問

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

  1. これは標準の有効な解釈ですか?とにかく、それは従来のものでも意図されたものでもないようです。
  2. 直感的に理解できますか?
  3. 私が見ていないというこの考えに問題はありますか?それが正確に予期されていないときにコピーが静かに作成される可能性があるようですが、それはとにかくC ++ 03の場所の現状です。また、それはいくつかを作りますオーバーロードは現在実行可能ではないときに実行可能ですが、実際には問題になるとは思いません。
  4. これは、GCCの実験的なパッチなどを作成する価値があるほど重要な改善ですか?
4

2 に答える 2

4

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

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

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

...ただし、13.3.3.1.4 / 3節では、左辺値が右辺値のref引数にバインドできないため、GCCとMSVCはどちらもg2を拒否します。私はこの背後にある理論的根拠を理解しています-それはN2831「右辺値参照による安全性の問題の修正」で説明されています。また、GCCの元のパッチは著者の1人(Doug Gregor)によって書かれたため、GCCはおそらくその論文の著者が意図したとおりにこの条項を実装していると思います。

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

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

移動可能な可能性のある多数の引数を受け入れる関数があり、可能であればそれらを移動し(たとえば、ファクトリ関数/コンストラクタ:Object create_object(string、vector、string)など)、移動またはコピーしたい場合必要に応じて各引数を使用すると、すぐに多くのコードを書き始めることができます。

あまり。なぜたくさんのコードを書きたいのですか?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);
}

...そしてすべてが順調です。特別なテンプレート引数推論ルールは、左辺値引数と右辺値引数を区別するのに役立ち、std :: forwardを使用すると、実際の引数と同じ「値」を持つ式を作成できます。したがって、次のように書くと、次のようになります。

string foo();

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

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

したがって、左辺値が暗黙的なコピーを介して右辺値にバインドされた場合、create_object(legacy_string &&、legacy_vector &&、legacy_string &&)のようなオーバーロードを1つだけ書き込むことができ、多かれ少なかれ、右辺値/左辺値参照オーバーロードのすべての組み合わせを提供するように機能します。 ..

ええと、それはパラメータを値で受け取る関数とほとんど同じです。冗談じゃない。

これは、GCCの実験的なパッチなどを作成する価値があるほど重要な改善ですか?

改善はありません。

于 2010-05-01T11:02:20.583 に答える
3

この質問であなたの主張がよくわかりません。移動可能なクラスがある場合は、Tバージョンが必要です。

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

また、クラスが従来型であるが効率的である場合は、スワップバージョンを作成するか、方法swapにフォールバックすることができますconst T&

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

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

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

私には、右辺値参照にバインドするためだけに、文字列左辺値をそれ自体の右辺値コピーに自動的に変換することは正しくないようです。右辺値参照は、右辺値にバインドすると言います。ただし、同じタイプの左辺値にバインドしようとすると、失敗する可能性が高くなります。隠しコピーを導入して、それを許可するのは私には正しく聞こえません。なぜなら、人々がaを見て、左辺値X&&を渡すとき、コピーがないこと、そしてそれが機能する場合、そのバインディングは直接であるとほとんどの人が期待するからです。Xユーザーが自分のコードを修正できるように、すぐに失敗する方がよいでしょう。

于 2010-05-01T10:54:04.027 に答える