ずっと前にC ++を学んだとき、C ++のポイントの一部は、ループに「ループ不変式」があるように、クラスにもオブジェクトの存続期間に関連する不変式があることを強く強調しました。オブジェクトが生きている限り。コンストラクターによって確立され、メソッドによって保持されるべきもの。カプセル化/アクセス制御は、不変条件を強制するのに役立ちます。RAIIは、このアイデアでできることの1つです。
C ++ 11以降、ムーブセマンティクスがあります。移動をサポートするクラスの場合、オブジェクトからの移動はその寿命を正式に終了することはありません-移動は「有効な」状態のままにすることになっています。
クラスの設計において、クラスの不変式が移動元までしか保持されないように設計するのは悪い習慣ですか?または、高速化できるようになれば大丈夫です。
具体的にするために、コピー不可だが移動可能なリソースタイプがあるとします。
class opaque {
opaque(const opaque &) = delete;
public:
opaque(opaque &&);
...
void mysterious();
void mysterious(int);
void mysterious(std::vector<std::string>);
};
そして、何らかの理由で、おそらく既存のディスパッチシステムで使用できるように、このオブジェクトのコピー可能なラッパーを作成する必要があります。
class copyable_opaque {
std::shared_ptr<opaque> o_;
copyable_opaque() = delete;
public:
explicit copyable_opaque(opaque _o)
: o_(std::make_shared<opaque>(std::move(_o)))
{}
void operator()() { o_->mysterious(); }
void operator()(int i) { o_->mysterious(i); }
void operator()(std::vector<std::string> v) { o_->mysterious(v); }
};
このcopyable_opaque
オブジェクトでは、構築時に確立されるクラスの不変式は、o_
デフォルトのアクタがなく、コピーアクタではない唯一のアクタがこれらを保証するため、メンバは常に有効なオブジェクトを指すことです。すべてのoperator()
メソッドは、この不変式が保持されることを前提とし、後で保持します。
ただし、オブジェクトの移動元の場合は、o_
何も指し示しません。そして、その後、いずれかのメソッドoperator()
を呼び出すと、UB /クラッシュが発生します。
オブジェクトが決して移動されない場合、不変式はdtor呼び出しまで保持されます。
仮に、このクラスを書いて、数ヶ月後に、私の想像上の同僚がUBを体験したとしましょう。そのメソッド。明らかに、それは1日の終わりに彼のせいですが、このクラスは「不十分に設計されていますか」
考え:
通常、C ++では、触れると爆発するゾンビオブジェクトを作成するのは不適切です。
オブジェクトを構築できない場合、不変式を確立できず、その後、ctorから例外をスローします。何らかの方法で不変式を保持できない場合は、何らかの方法でエラーを通知し、ロールバックします。これは、移動元オブジェクトと異なる必要がありますか?「このオブジェクトが移動された後、それを破壊する以外に何かを行うことは違法(UB)です」とヘッダーに文書化するだけで十分ですか?
各メソッド呼び出しで有効であると継続的にアサートする方が良いですか?
そのようです:
class copyable_opaque {
std::shared_ptr<opaque> o_;
copyable_opaque() = delete;
public:
explicit copyable_opaque(opaque _o)
: o_(std::make_shared<opaque>(std::move(_o)))
{}
void operator()() { assert(o_); o_->mysterious(); }
void operator()(int i) { assert(o_); o_->mysterious(i); }
void operator()(std::vector<std::string> v) { assert(o_); o_->mysterious(v); }
};
アサーションは動作を実質的に改善せず、スローダウンを引き起こします。プロジェクトが常にアサーションで実行されるのではなく、「リリースビルド/デバッグビルド」スキームを使用する場合、リリースビルドのチェックにお金を払わないため、これはより魅力的だと思います。デバッグビルドが実際にない場合、これはかなり魅力的ではないように見えます。
- クラスをコピー可能にするのが良いのですが、移動できないようにしますか?
これも悪いように見え、パフォーマンスに影響を与えますが、「不変」問題を簡単に解決します。
ここで関連する「ベストプラクティス」は何だと思いますか?