1. ホーム
  2. c++

Herb Sutter氏のCppCon 2014の講演(Back to Basics: Modern C++ Style)で、値を取るセッター・メンバー関数が推奨されていないのはなぜか?

2023-11-19 17:07:36

疑問点

Herb Sutter の CppCon 2014 の講演で、Back to Basics: モダン C++ スタイルについて、彼はスライド 28 で言及しています ( スライドのウェブコピーはこちら )でこのパターンに言及しています。

class employee {
  std::string name_;
public:
  void set_name(std::string name) noexcept { name_ = std::move(name); }
};

彼は、テンポラリで set_name() を呼び出すと noexcept-ness が強くならないため、これが問題になると言っています (彼は "noexcept-ish" というフレーズを使用しています)。

さて、私は最近の自分の C++ コードで上記のパターンをかなり多用しています。これは主に、毎回 set_name() のコピーを 2 つ入力する必要がなくなるからです。しかし、Herbのフレーズ"このnoexceptは 問題あり std::string の移動代入演算子は noexcept であり、そのデストラクタも noexcept なので、上の set_name() は noexcept が保証されているように見えます。しかし、コンパイラが例外を投げる可能性があります。 の前に set_name() がパラメータを準備するときに例外を投げる可能性があることがわかりますが、私はそれが問題であると見なすのに苦労しています。

後のスライド32で、ハーブは上記がアンチパターンであることを明確に述べています。私が怠惰になることで悪いコードを書いてしまわないように、誰かその理由を説明してくれませんか?

どのように解決するのか?

他の人がカバーした noexcept の推論は上記の通りです。

Herbは効率性の側面について、講演の中でより多くの時間を費やしました。問題は、割り当てではなく、不必要な再割り当てにあります。あなたが1つの std::string を別の文字列にコピーするとき、コピーされるデータを保持するのに十分なスペースがあれば、コピールーチンはコピー先の文字列の割り当てられたストレージを再利用します。移動の割り当てを行う場合、移動先の文字列の既存のストレージは、元の文字列からストレージを引き継ぐので、割り当てを解除する必要があります。コピー&ムーブのイディオムでは、テンポラリを渡さなかった場合でも、常に割り当て解除を行わなければなりません。これが、この講演の後半で示される、恐ろしいパフォーマンスの原因です。彼のアドバイスは、代わりに const ref を受け取り、もしそれが必要だと判断したら、r-value 参照のオーバーロードを持つことでした。これにより、非一時的なものについては既存のストレージにコピーして割り当て解除を回避し、一時的なものについては移動して割り当て解除の代金を支払うという、両方の利点が得られます (移動前に移動先が割り当て解除を行うか、コピー後に移動元が割り当て解除を行うかです)。

メンバ変数に割り当てを解除するストレージがないため、上記はコンストラクタには適用されません。コンストラクタはしばしば複数の引数を取り、各引数の const ref/r-value ref オーバーロードを行う必要がある場合、コンストラクタのオーバーロードが組み合わせ的に爆発してしまうので、これは良いことです。

ここで問題になるのは、コピー時に std::string のようなストレージを再利用するクラスがどれだけあるかということです。std::vector はそうだと思いますが、それ以外ではよくわかりません。私はこのようにストレージを再利用するクラスを書いたことがありませんが、文字列やベクターを含むクラスはたくさん書いています。Herbのアドバイスに従えば、ストレージを再利用しないクラスでも問題ありません。最初はシンク関数のコピーバージョンでコピーを行い、もしコピーがパフォーマンスヒットになりすぎると判断したら、r-value参照のオーバーロードを作ってコピーを回避します(std::stringの場合と同じように)。一方、std::stringやその他のストレージを再利用する型では、quot;copy-and-move"を使うとパフォーマンスヒットを実証しており、おそらくほとんどの人のコードでこれらの型が多く使用されているはずです。私は今のところ Herb のアドバイスに従っていますが、この問題が完全に解決したと考える前に、もう少し考え抜く必要があります (おそらく、このすべてに書く時間のないブログ投稿が潜んでいるはずです)。