15

Bjarne Stroustrup の CoreCppGuidelines を読んでいるときに、私の経験と矛盾するガイドラインを見つけました。

C.21には以下が必要です。

または=deleteデフォルトの操作を定義する場合は、または=deleteそれらすべてを定義します

次の理由で:

特殊関数のセマンティクスは密接に関連しているため、1 つをデフォルト以外にする必要がある場合、他の関数も変更する必要がある可能性があります。

私の経験から、デフォルト操作の再定義の最も一般的な 2 つの状況は次のとおりです。

#1: 継承を許可するデフォルトの本体を持つ仮想デストラクタの定義:

class C1
{
...
    virtual ~C1() = default;
}

#2: RAII 型のメンバーの初期化を行うデフォルト コンストラクターの定義:

class C2
{
public:
    int a; float b; std::string c; std::unique_ptr<int> x;

    C2() : a(0), b(1), c("2"), x(std::make_unique<int>(5))
    {}
}

私の経験では、他のすべての状況はめったにありませんでした。

これらの例についてどう思いますか。それらは C.21 規則の例外ですか、それともすべてのデフォルト操作をここで定義する方がよいでしょうか? 他によくある例外はありますか?

4

2 に答える 2

12

私はこのガイドラインに重大な懸念を持っています。ガイドラインであってルールではないことを知っていても、まだ懸念があります。

std::complex<double>、またはに似たユーザー作成クラスがあるとしますstd::chrono::seconds。単なる値型です。リソースを所有していません。単純であることを意図しています。特別なメンバーではないコンストラクターがあるとしましょう。

class SimpleValue
{
    int value_;
public:
    explicit SimpleValue(int value);
};

さて、私もSimpleValueデフォルトで構築可能にしたいので、別のコンストラクターを提供することでデフォルトのコンストラクターを抑制したので、その特別なメンバーを追加する必要があります:

class SimpleValue
{
    int value_;
public:
    SimpleValue();
    explicit SimpleValue(int value);
};

人々がこのガイドラインと理由を覚えてしまうのではないかと心配しています: まあ、特別なメンバーを 1 つ提供したので、残りを定義または削除する必要があります。

class SimpleValue
{
    int value_;
public:
    ~SimpleValue() = default;
    SimpleValue();
    SimpleValue(const SimpleValue&) = default;
    SimpleValue& operator=(const SimpleValue&) = default;

    explicit SimpleValue(int value);
};

うーん...移動メンバーは必要ありませんが、賢い人が私に言ったことに無頓着に従う必要があるので、それらを削除します:

class SimpleValue
{
    int value_;
public:
    ~SimpleValue() = default;
    SimpleValue();
    SimpleValue(const SimpleValue&) = default;
    SimpleValue& operator=(const SimpleValue&) = default;
    SimpleValue(SimpleValue&&) = delete;
    SimpleValue& operator=(SimpleValue&&) = delete;

    explicit SimpleValue(int value);
};

CoreCppGuidelines C.21 によって、このような大量のコードが生成されるのではないかと心配しています。なぜそれが悪いのですか?いくつかの理由:

1. これは、この正しいバージョンよりもはるかに読みにくいです。

class SimpleValue
{
    int value_;
public:
    SimpleValue();
    explicit SimpleValue(int value);
};

2.壊れてい ます。SimpleValue関数から値によってa を初めて返そうとすると、次のようになります。

SimpleValue
make_SimpleValue(int i)
{
    // do some computations with i
    SimpleValue x{i};
    // do some more computations
    return x;
}

これはコンパイルされません。の削除されたメンバーへのアクセスに関するエラー メッセージが表示されますSimpleValue

私はいくつかのより良いガイドラインを持っています:

1. コンパイラがいつ特別なメンバーをデフォルト化または削除するのか、およびデフォルト化されたメンバーが何をするのかを知っておいてください。

このチャートはそれを助けることができます:

ここに画像の説明を入力

このチャートが複雑すぎる場合は、理解できます複雑です。しかし、少しずつ説明してくれると、対処しやすくなります。 このグラフを説明しているビデオへのリンクを付けて、1 週間以内にこの回答を更新できることを願っています。 説明へのリンクは、私が望んでいたよりも長く遅れた後です (私の謝罪): https://www.youtube.com/watch?v=vLinb2fgkHk

2. コンパイラの暗黙のアクションが正しくない場合は、必ず特殊メンバーを定義または削除してください。

3. 非推奨の動作 (上のグラフの赤いボックス) に依存しないでください。デストラクタ、コピー コンストラクタ、またはコピー代入演算子のいずれかを宣言する場合は、コピー コンストラクタとコピー代入演算子の両方を宣言します。

4. 移動メンバーを削除しないでください。そうした場合、せいぜい冗長になります。最悪の場合、クラスが壊れます (SimpleValue上記の例のように)。ムーブ メンバーを削除した場合、それは冗長なケースであり、読者にクラスを常に見直して、それが壊れたケースではないことを確認するように強制します。

5. 6 つの特別なメンバーのそれぞれに優しく愛情を込めて注意を払ってください。その結果、コンパイラーに処理を任せることになる場合でも (おそらくそれらを暗黙的に禁止または削除することによって)。

6. 特別なメンバーを一貫した順序でクラスの先頭 (明示的に宣言したいもののみ) に配置して、読者がそれらを検索する必要がないようにします。私は私のお気に入りのオーダーを持っています。あなたの好みのオーダーが違っていても構いません。私の好みの順序は、SimpleValue例で使用した順序です。

このスタイルのクラス宣言について、より多くの理論的根拠を示した簡単な論文を次に示します。

于 2016-07-31T18:23:25.693 に答える