最近、私は cppreference .../vector/emplace_back から例を読みました:
struct President
{
std::string name;
std::string country;
int year;
President(std::string p_name, std::string p_country, int p_year)
: name(std::move(p_name)), country(std::move(p_country)), year(p_year)
{
std::cout << "I am being constructed.\n";
}
私の質問:これは本当にstd::move
が必要ですか?私の要点は、このp_name
はコンストラクターの本体では使用されないということです。おそらく、言語には移動のセマンティクスをデフォルトで使用するルールがあるのでしょうか?
これは、初期化リストにstd :: moveをすべての重いメンバー(std::string
、std::vector
など)に追加するのは本当に面倒です。 C++ 03で書かれた何百ものKLOCプロジェクトを想像してみてください-これをどこにでも追加しますかstd::move
?
この質問: move-constructor-and-initialization-list 答えは言う:
ゴールデンルールとして、右辺値参照で何かを取る場合は常にstd :: move内で使用する必要があり、ユニバーサル参照(つまり&&を使用した推定テンプレート型)で使用する場合は常にstd ::内で使用する必要があります。フォワード
しかし、私は確信がありません。値渡しはむしろ普遍的な参照ではありませんか?
[UPDATE]
私の質問をより明確にするため。コンストラクタの引数をXValueとして扱うことはできますか?値の期限切れを意味しますか?
この例のAFAIKでは、std::move
を使用していません。
std::string getName()
{
std::string local = "Hello SO!";
return local; // std::move(local) is not needed nor probably correct
}
それで、ここでそれが必要でしょうか:
void President::setDefaultName()
{
std::string local = "SO";
name = local; // std::move OR not std::move?
}
私にとって、このローカル変数は期限切れの変数です-したがって、移動のセマンティクスを適用できます...これは、値で渡される引数に似ています...
私の質問:このstd :: moveは本当に必要ですか?私のポイントは、コンパイラーがこのp_nameがコンストラクターの本体で使用されていないことを認識していることです。おそらく、デフォルトで移動セマンティクスを使用するいくつかのルールがありますか?
一般に、左辺値を右辺値に変換したい場合、はい、std::move()
が必要です。参照 C++ 11コンパイラは、コードの最適化時にローカル変数を右辺値に変換できますか?
_void President::setDefaultName()
{
std::string local = "SO";
name = local; // std::move OR not std::move?
}
_
私にとって、このローカル変数は期限切れの変数です-したがって、移動のセマンティクスを適用できます...これは、値で渡される引数に似ています...
ここで、オプティマイザが余分なlocal
ALTOGETHERを削除するようにしたいと思います。残念ながら、実際にはそうではありません。 heapメモリが再生されると、コンパイラの最適化が難しくなります。参照 BoostCon 2013 Keynote:Chandler Carruth:Optimizing the Emergent Structures of C++ 。チャンドラーの話から私の気を引いたことの1つは、ヒープに割り当てられたメモリに関しては、オプティマイザは単純に諦める傾向があるということです。
残念な例については、以下のコードを参照してください。この例では_std::string
_を使用していません。インラインアセンブリコードを使用して大幅に最適化されたクラスであり、直感に反するコードが生成されることが多いためです。侮辱に傷害を加えるために、_std::string
_はおおまかに言ってgcc 4.7.2で参照カウント共有ポインターを大まかに言っています( copy-on-write最適化 、_std::string
_)。したがって、_std::string
_のないサンプルコード:
_#include <algorithm>
#include <cstdio>
int main() {
char literal[] = { "string literal" };
int len = sizeof literal;
char* buffer = new char[len];
std::copy(literal, literal+len, buffer);
std::printf("%s\n", buffer);
delete[] buffer;
}
_
明らかに、「as-if」ルールに従って、生成されたコードはこれに最適化できます。
_int main() {
std::printf("string literal\n");
}
_
リンク時最適化を有効にして(LTO)GCC 4.9.0とClang 3.5で試してみましたが、どれもコードをこのレベルに最適化できませんでした。生成されたアセンブリコードを確認しました。どちらもヒープにメモリを割り当て、コピーを行いました。まあ、ええ、それは残念です。
スタック割り当てられたメモリは異なります:
_#include <algorithm>
#include <cstdio>
int main() {
char literal[] = { "string literal" };
const int len = sizeof literal;
char buffer[len];
std::copy(literal, literal+len, buffer);
std::printf("%s\n", buffer);
}
_
アセンブリコードを確認しました。ここでは、コンパイラはコードをbasicallyにstd::printf("string literal\n");
に削減できます。
したがって、サンプルコードの余分なlocal
を排除できるという私の期待は完全にはサポートされていません。スタックが割り当てられた配列を使用した後者の例が示すように、それを行うことができます。
C++ 03で記述された何百ものKLOCプロジェクトを想像してみてください-この_
std::move
_のどこにでも追加しますか?
[...]
しかし、私にはわかりません:値による受け渡しは、むしろ普遍的な参照ではありませんか?
最適化を行ってコードが遅くなったことを知るだけで、最適化を行っている状況に簡単に気づくでしょう。 :(私のアドバイスはハワード・ヒナントの測定と同じです。
_std::string getName()
{
std::string local = "Hello SO!";
return local; // std::move(local) is not needed nor probably correct
}
_
はい。ただし、この特殊なケースにはルールがあります。これは、名前付き戻り値の最適化(NRVO)と呼ばれます。
DR1579 によって修正された現在のルールは、NRVOableローカルまたはパラメーター、またはid-expressionローカル変数またはパラメーターを参照することは、return
ステートメントの引数です。
これが機能するのは、return
ステートメントの後に変数を再び使用できないためです。
それがそうでないことを除いて:
struct S {
std::string s;
S(std::string &&s) : s(std::move(s)) { throw std::runtime_error("oops"); }
};
S foo() {
std::string local = "Hello SO!";
try {
return local;
} catch(std::exception &) {
assert(local.empty());
throw;
}
}
したがって、return
ステートメントの場合でも、そのステートメントに現れるローカル変数またはパラメーターがその最後の使用であることは実際には保証されません変数。
ローカル変数の「最後の」使用がxvalue変換の対象になることを指定するために標準を変更できることは完全に問題外ではありません。問題は、「最後の」使用法が何であるかを定義することです。もう1つの問題は、これが関数内で非局所的な影響を与えることです。追加する下位のデバッグステートメントは、依存していたxvalue変換が発生しなくなったことを意味します。単一のステートメントは複数回実行される可能性があるため、単一のオカレンスルールでも機能しません。
Std-proposalsメーリングリストでの議論のための提案を提出することに興味がありますか?
私の質問:このstd :: moveは本当に必要ですか?私のポイントは、このp_nameがコンストラクターの本体で使用されていないということです。おそらく、言語にデフォルトで移動セマンティクスを使用するいくつかのルールがありますか?
もちろん必要です。 p_name
は左辺値なので、std::move
は、右辺値に変換して移動コンストラクタを選択するために必要です。
それは言語が言うことだけではありません-タイプが次のような場合はどうでしょうか:
struct Foo {
Foo() { cout << "ctor"; }
Foo(const Foo &) { cout << "copy ctor"; }
Foo(Foo &&) { cout << "move ctor"; }
};
言語ではcopy ctor
移動を省略した場合は印刷する必要があります。ここにはオプションはありません。コンパイラーはこれを別の方法で行うことはできません。
はい、コピー省略が引き続き適用されます。しかし、あなたの場合(初期化リスト)ではありません。コメントを参照してください。
答えは、移動から利益を得て、引数の組み合わせの爆発を避けながら、渡された引数のコピーを保存したいときに安全なパターンを提供することです。
2つの文字列(つまり、コピーする2つの「重い」オブジェクト)を保持するこのクラスを考えます。
struct Foo {
Foo(string s1, string s2)
: m_s1{s1}, m_s2{s2} {}
private:
string m_s1, m_s2;
};
それでは、さまざまなシナリオで何が起こるか見てみましょう。
string s1, s2;
Foo f{s1, s2}; // 2 copies for passing by value + 2 copies in the ctor
ああ、これは悪いです。ここでは、実際に必要なのは2つだけで、4つのコピーが発生します。 C++ 03では、Foo()引数をconst-refsにすぐに変換します。
Foo(const string &s1, const string &s2) : m_s1{s1}, m_s2{s2} {}
今私たちは持っています
Foo f{s1, s2}; // 2 copies in the ctor
それははるかに良いです!
しかし、動きはどうですか?たとえば、一時的なものから:
string function();
Foo f{function(), function()}; // 2 moves + still 2 copies in the ctor
または、明示的にlvalueをctorに移動する場合:
Foo f{std::move(s1), std::move(s2)}; // 2 moves + still 2 copies in the ctor
それは良いことではありません。string
のムーブトラクターを使用して、Foo
メンバーを直接初期化することもできます。
したがって、Fooのコンストラクターにいくつかのオーバーロードを導入できます。
Foo(const string &s1, const string &s2) : m_s1{s1}, m_s2{s2} {}
Foo(string &&s1, const string &s2) : m_s1{s1}, m_s2{s2} {}
Foo(const string &s1, string &s2) : m_s1{s1}, m_s2{s2} {}
Foo(string &&s1, string &&s2) : m_s1{s1}, m_s2{s2} {}
だから、わかりました、今私たちは持っています
Foo f{function(), function()}; // 2 moves
Foo f2{s1, function()}; // 1 copy + 1 move
良い。しかし、一体、組み合わせ爆発が発生します:ありとあらゆる引数がconst-ref + rvalueバリアントに現れる必要があります。 4つの文字列を取得した場合はどうなりますか? 16人の俳優を書くつもりですか?
代わりに見てみましょう:
Foo(string s1, string s2) : m_s1{std::move(s1)}, m_s2{std::move(s2)} {}
このバージョンでは:
Foo foo{s1, s2}; // 2 copies + 2 moves
Foo foo2{function(), function()}; // 2 moves in the arguments + 2 moves in the ctor
Foo foo3{std::move(s1), s2}; // 1 copy, 1 move, 2 moves
動きは非常に安いので、このパターンはそれらから完全に利益を得るおよび組み合わせ爆発を回避します。確かにずっと下に移動できます。
そして、私は例外的な安全の表面を傷つけることすらしませんでした。
より一般的な説明の一部として、次のスニペットを考えてみましょう。関連するすべてのクラスが、値渡しによるsのコピーを作成します。
{
// some code ...
std::string s = "123";
AClass obj {s};
OtherClass obj2 {s};
Anotherclass obj3 {s};
// s won't be touched any more from here on
}
私があなたを正しく知っていれば、コンパイラが最後にs
を実際に使用していないことを本当に望んでいます:
{
// some code ...
std::string s = "123";
AClass obj {s};
OtherClass obj2 {s};
Anotherclass obj3 {std::move(s)}; // bye bye s
// s won't be touched any more from here on.
// hence nobody will notice s is effectively in a "dead" state!
}
なぜコンパイラできないがそうするのかを説明しましたが、私はあなたの主張を理解します。特定の観点からは理にかなっています-s
を最後の使用よりも長く存続させることはナンセンスです。 C++ 2xを考えるための食べ物だと思います。
さらに調査を行い、ネット上の別のフォーラムに問い合わせました。
残念ながら、これはstd::move
が必要なのは、C++標準でそう言われているだけでなく、そうでなければ危険です。
((comp.std.c ++からKalle Olavi Niemitaloへのクレジット- ここに彼の答え ))
#include <memory>
#include <mutex>
std::mutex m;
int i;
void f1(std::shared_ptr<std::lock_guard<std::mutex> > p);
void f2()
{
auto p = std::make_shared<std::lock_guard<std::mutex> >(m);
++i;
f1(p);
++i;
}
F1(p)が自動的にf1(std :: move(p))に変更された場合、ミューテックスは2番目の++ iの前にすでにロック解除されます。ステートメント。
次の例の方が現実的です。
#include <cstdio>
#include <string>
void f1(std::string s) {}
int main()
{
std::string s("hello");
const char *p = s.c_str();
f1(s);
std::puts(p);
}
F1(s)が自動的にf1(std :: move(s))に変更された場合、f1が戻った後、ポインターpは無効になります。