5のルール-それを使用するかどうか?


20

3 のルール(新しいc ++標準の5のルール)の状態:

デストラクタ、コピーコンストラクタ、またはコピー割り当て演算子のいずれかを明示的に宣言する必要がある場合は、おそらく3つすべてを明示的に宣言する必要があります。

しかし、一方で、マーティンの「クリーンコード」は、すべての空のコンストラクターとデストラクターを削除することを推奨しています(293ページ、G12:Clutter)。

実装されていないデフォルトのコンストラクタはどのような用途に使用されますか?役目を果たすのは、意味のないアーティファクトでコードを混乱させることです。

だから、これらの2つの反対意見をどのように扱うのですか?空のコンストラクタ/デストラクタを実際に実装する必要がありますか?


次の例は、まさに私が意味することを示しています。

#include <iostream>
#include <memory>

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    ~A(){}
    A( const A & other ) : v( new int( *other.v ) ) {}
    A& operator=( const A & other )
    {
        v.reset( new int( *other.v ) );
        return *this;
    }

    std::auto_ptr< int > v;
};
int main()
{
    const A a( 55 );
    std::cout<< "a value = " << *a.v << std::endl;
    A b(a);
    std::cout<< "b value = " << *b.v << std::endl;
    const A c(11);
    std::cout<< "c value = " << *c.v << std::endl;
    b = c;
    std::cout<< "b new value = " << *b.v << std::endl;
}

g ++ 4.6.1を次のように使用して正常にコンパイルします。

g++ -std=c++0x -Wall -Wextra -pedantic example.cpp

のデストラクタstruct Aは空であり、実際には必要ありません。だから、そこにあるべきですか、それとも削除されるべきですか?


15
2つの引用は、異なることについて語っています。または、あなたの主張を完全に見逃しています。
ベンジャミンバニエ

1
@honk私のチームのコーディング標準では、4つすべて(コンストラクタ、デストラクタ、コピーコンストラクタ)を常に宣言するルールがあります。本当に理にかなっているのだろうかと思っていました。デストラクタが空であっても、常にデストラクタを宣言する必要がありますか?
BЈовић

空のdesctructorについては、codesynthesis.com / 〜boris / blog / 2012/04/04 / について考えてください。そうでない場合は(5)が、私には1が4のルールをしたいと思う理由は考えて完璧な理にかなっていない3の法則
ベンジャミンBannier

@honkネットで見つけた情報に注意してください。すべてが真実ではありません。例えば、virtual ~base () = default;(正当な理由で)コンパイルされません
BЈовић

@VJovic、いいえ、仮想にする必要がない限り、空のデストラクタを宣言する必要はありません。そして、私たちが主題に取り組んでいる間、あなたはauto_ptrどちらも使用すべきではありません。
ディマ

回答:


44

最初は、ルールに「おそらく」と書かれているため、常に適用されるとは限りません。

私がここで見る2番目のポイントは、3つのうちの1つを宣言する必要がある場合、それはメモリの割り当てなどの特別なことをしているためです。この場合、他のメンバーは同じタスク(コピーコンストラクターで動的に割り当てられたメモリのコンテンツをコピーする、またはそのようなメモリを解放するなど)を処理する必要があるため、空になりません。

したがって、結論として、空のコンストラクターまたはデストラクターを宣言するべきではありませんが、1つが必要な場合、他のコンストラクターも必要になる可能性が非常に高くなります。

あなたの例として:そのような場合、デストラクタを除外できます。明らかに何もしません。スマートポインターの使用は、3のルールが当てはまらない場所と理由の完璧な例です。

これは、見逃したかもしれない重要な機能を実装するのを忘れた場合に備えて、コードを再検討する場所のガイドにすぎません。


スマートポインターを使用すると、ほとんどの場合、デストラクターは空になります(ほとんどすべてのクラスがpimplイディオムを使用するため、コードベース内のデストラクターの99%以上は空です)。
BЈовић

うわー、それは私がそれを臭いと呼ぶだろうと非常にポン引きです。多くのコンパイラでは、にきびは最適化が難しくなります(たとえば、インライン化が難しくなります)。
ベンジャミンバニエ

@honk「にきびが多いコンパイラ」とはどういう意味ですか?:)
BЈовић

@VJovic:ごめんなさい、タイプミス:「
にきび

4

ここには本当に矛盾はありません。3のルールは、デストラクタ、コピーコンストラクタ、およびコピー割り当て演算子について説明しています。ボブおじさんは、空のデフォルトコンストラクターについて話します。

デストラクタが必要な場合、クラスにはおそらく動的に割り当てられたメモリへのポインタが含まれており、コピーctorとoperator=()ディープコピーを実行する必要があります。これは、デフォルトのコンストラクタが必要かどうかに完全に直交しています。

また、C ++では、空であってもデフォルトのコンストラクタが必要な場合があることに注意してください。クラスにデフォルト以外のコンストラクターがあるとします。その場合、コンパイラーはデフォルトのコンストラクターを生成しません。つまり、このクラスのオブジェクトはSTLコンテナーに格納できません。これらのコンテナーはオブジェクトがデフォルトで構築可能であることを期待しているためです。

一方、クラスのオブジェクトをSTLコンテナーに配置する予定がない場合、空のデフォルトコンストラクターは無駄です。


2

ここで、デフォルトの1つのコンストラクタ/割り当て/デストラクタに相当する潜在的な(*)には目的があります。問題についてあなたが持っている事実を文書化し、デフォルトの動作が正しいと判断しました。ところで、C ++ 11では、物事は=defaultその目的に役立つかどうかを知るのに十分なほど安定していません。

(別の潜在的な目的があります。デフォルトのインライン定義の代わりにアウトライン定義を提供します。何らかの理由がある場合は明示的に文書化することをお勧めします)。

(*)潜在的な理由は、3つのルールが適用されなかった実際のケースを思い出せないためです。


例の追加後に編集します。auto_ptrを使用した例は興味深いものです。使用しているのはスマートポインターですが、仕事に合ったものではありません。特に状況が頻繁に発生する場合は、あなたがやったことをやるよりもむしろ書きたいと思います。(私が間違っていなければ、標準もブーストも提供しません)。


例は私のポイントを示しています。デストラクタは実際には必要ありませんが、3のルールは、デストラクタが存在することを示しています。
BЈовић

1

5のルールは、3のルールの因果的拡張であり、オブジェクトの誤用に対する因果的動作です。

デストラクタが必要な場合は、デフォルト以外の「リソース管理」を行ったことを意味します(値を作成および破棄するだけです)。

デフォルトのコピーによってコピー、割り当て、移動および転送ので、あなただけ保持されていない場合は、値を、あなたは何をすべきかを定義する必要があります。

つまり、C ++は、移動を定義するとコピーを削除し、コピーを定義すると移動を削除します。ほとんどの場合、値をエミュレートするかどうかを定義する必要があります(そのため、copy mutリソースをクローンし、移動しても意味がありません)またはリソースマネージャー(したがって、リソースを移動します、コピーは意味がありません:ルール3のルールが他の3のルールになります)

コピーと移動の両方を定義する必要がある場合(5の規則)は非常にまれです。通常、「大きな値」があり、別個のオブジェクトに指定した場合はコピーする必要がありますが、一時オブジェクトから取得した場合は移動できます(回避すること)クローンはその後、破壊します)。これは、STLコンテナーまたは算術コンテナーの場合です。

場合は、マトリックスになります彼らはので、コピーをサポートする必要があり、値(a=b; c=b; a*=2; b*=3;お互いに影響を与えてはならない)が、彼らは(移動もサポートすることにより、最適化することが可能でa = 3*b+4*cあり+2つの一時を取り、一時的に生成するが:避けクローンと削除が可能有用)


1

私は3つのルールの別の言い回しを好みます。これはより合理的で、「クラスにデストラクタ(空の仮想デストラクタ以外)が必要な場合は、おそらくコピーコンストラクタと代入演算子も必要です。」

デストラクタからの一方向の関係として指定すると、いくつかのことが明確になります。

  1. デフォルト以外のコピーコンストラクターまたは割り当て演算子を最適化としてのみ提供する場合には適用されません。

  2. ルールの理由は、デフォルトのコピーコンストラクタまたは割り当て演算子が手動のリソース管理を台無しにする可能性があるためです。リソースを手動で管理している場合は、それらを解放するためにデストラクタが必要であることに気付くでしょう。


-3

議論でまだ言及されていない別のポイントがあります:デストラクタは常に仮想であるべきです。

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    virtual ~A(){}
    ...
}

すべての派生クラスでもコンストラクターを仮想化するには、コンストラクターを基本クラスで仮想として宣言する必要があります。そのため、基本クラスにデストラクタが必要ない場合でも、空のデストラクタを宣言して実装することになります。

(-Wall -Wextra -Weffc ++)にすべての警告を付けると、g ++はこれについて警告します。クラスが最終的に基本クラスになるかどうかは分からないので、常にどのクラスでも仮想デストラクタを宣言することをお勧めします。仮想デストラクタが必要ない場合、害はありません。そうである場合、エラーを見つける時間を節約します。


1
しかし、仮想コンストラクタは必要ありません。それを行うと、メソッドへのすべての呼び出しで仮想ディスパッチが使用されます。ところで、C ++には「仮想コンストラクター」などはないことに注意してください。また、非常に高い警告レベルとして例をコンパイルしました。
BЈовић

gccが警告に使用するルールであるIIRCと、とにかく一般的に従うルールは、クラス内に他の仮想メソッドがある場合、仮想デストラクタがあるべきだということです。
ジュール14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.