10

C++ の既定の動作と同じことを行うコードを明示的に記述する理由があるかどうか疑問に思っています。

ここにいくつかのコードがあります:

class BaseClass
{
public:
    virtual ~BaseClass() {}

    virtual void f() { /* do something */ }
};

class ExplicitClass
    : public BaseClass
{
public:
    ExplicitClass()
        : BaseClass()   // <-- explicit call of base class constructor
    {
        // empty function
    }

    virtual ~ExplicitClass() {}  // <-- explicit empty virtual destructor

    virtual void f() { BaseClass::f(); }  // <-- function just calls base
};

class ImplicitClass
    : public BaseClass
{    
};

私は主に、リファクタリングとコード ベースの変更の領域に興味があります。多くのコーダーがこのようなコードを書くつもりはないと思いますが、時間の経過とともにコードが変更されると、最終的にこのようになる可能性があります。

に存在するコードを残すことに何か意味はありますExplicitClassか? 何が起こっているのかを示すというボーナスはわかりますが、糸くずが発生しやすく、危険であると思われます。

個人的には、デフォルトの動作コード ( などImplicitClass) であるコードを削除することを好みます。

いずれかの方法を支持するコンセンサスはありますか?

4

4 に答える 4

1

この問題には 2 つのアプローチがあります。

  1. コンパイラが同じものを生成したとしても、すべてを定義し、
  2. コンパイルがうまくいくものは何も定義しません。

(1)の信者は、「デフォルトのc-tor、コピーC-tor、代入演算子、およびd-torを常に定義する」などのルールを使用しています。

(1) 信者は、何かを見逃すよりも多く持っている方が安全だと考えている. 残念ながら、(1) はマネージャーに特に好まれています。このような「常にビッグ 4 を定義する」などのルールは、「コーディング標準」に進み、従わなければなりません。

私は(2)を信じています。そして、そのようなコーディング標準が存在する企業に対しては、私は常にコメントを入れています。

于 2012-09-21T00:22:14.633 に答える
0

それは、プログラムをどのように構造化し、読みたいかによって異なります。もちろん、それぞれに好みや理由があります。

class ExplicitClass
    : public BaseClass
{
public:

初期化は非常に重要です。ベースまたはメンバーを初期化しないと、警告が生成される可能性があります。当然のことながら、場合によってはバグをキャッチすることもあります。そのため、警告のコレクションが有効になっている場合、これは本当に理にかなっています。警告レベルを高く保ち、警告のカウントダウンを行います。また、意図を示すのにも役立ちます。

    ExplicitClass()
        : BaseClass()   // <-- explicit call of base class constructor
    {
        // empty function
    }

空の仮想デストラクタは、IME をエクスポートするのに統計的に最適な場所virtualです (もちろん、その定義は、複数の翻訳に表示される場合は別の場所にあります)。大量の rtti および vtable 情報があるため、これをエクスポートする必要があります。これらの情報は、バイナリで不要な肥大化を引き起こす可能性があります。この理由から、実際には空のデストラクタを非常に定期的に定義しています。

    virtual ~ExplicitClass() {}  // <-- explicit empty virtual destructor

おそらくそれはあなたのグループの慣習であるか、実装がまさにそのように設計されていることを文書化しています。これは、大規模なコードベースや複雑な階層内で (主観的に) 役立ちます。これは、型が採用することが期待される動的インターフェイスを思い出させるのにも役立つためです。すべてのクラスの動的実装を 1 か所で確認できるため、サブクラス内のすべての宣言を好む人もいます。そのため、クラス階層/インターフェイスがプログラマーの精神的なスタックよりも大きい場合、局所性は彼らを助けます。デストラクタと同様に、この仮想も typeinfo をエクスポートするのに適した場所になる場合があります。

    virtual void f() { BaseClass::f(); }  // <-- function just calls base
};

もちろん、資格があるかどうかだけを定義すると、プログラムや理論的根拠に従うのが難しくなります。空のデストラクタが毎回エクスポートされる理由を文書化するよりも明確であるため、慣例に固執するだけで、従うのが簡単なコードベースになる可能性があります。

最後の理由 (これは両方向に振れます) は、明示的な既定の定義により、ビルドとリンクの時間が増減する可能性があるためです。

幸いなことに、デフォルトおよび削除されたメソッドとコンストラクターを指定する方が簡単で明確になりました。

于 2012-09-21T01:55:59.630 に答える
0

コンセンサスに関する質問なので答えられませんが、ildjarn さんのコメントは面白くて正しいと思います。

そのように書く理由があるかどうかの質問によると、明示的クラスと暗黙的クラスが同じように動作するわけではありません。人々は、「メンテナンス」の理由でそれを行うことがあります。たとえば、派生fが別の方法で実装され、基本クラスを呼び出すことを忘れない場合などです。私は個人的に、これは役に立たないと思います。

于 2012-09-20T23:03:25.860 に答える
0

そこで実際に何が起こっているのか、また関数を自分で書かないことで発生する可能性のある問題を理解している限り、どちらの方法でも問題ありません。

例外安全性:

throwコンパイラは、必要な条件を暗黙的に追加して関数を生成します。暗黙的に作成されたコンストラクターの場合、これは基本クラスとメンバーからのすべてのスロー条件になります。

不正なコード

自動的に生成されたメンバー関数の一部が不適切な形式になるというトリッキーなケースがいくつかあります。次に例を示します。

class Derived;

class Base
{
public:
  virtual Base& /* or Derived& */
  operator=( const Derived& ) throw( B1 );

  virtual ~Base() throw( B2 );
};

class Member
{
public:
  Member& operator=( const Member& ) throw( M1 );
  ~Member() throw( M2 );
};

class Derived : public Base
{
  Member m_;
  //   Derived& Derived::operator=( const Derived& )
  //            throw( B1, M1 ); // error, ill-formed
  //   Derived::~Derived()
  //            throw( B2, M2 ); // error, ill-formed
};

そのoperator=throw ディレクティブは、少なくともその基本クラスと同じくらい制限的でなければならないため、 は不適切な形式です。つまり、B1 をスローするか、何もスローしない必要があります。Derived オブジェクトは Base オブジェクトと見なすこともできるため、これは理にかなっています。

呼び出さない限り、不正な形式の関数を持つことは完全に合法であることに注意してください。

私は基本的にここで GotW #69 を書き直しているので、詳細が必要な場合はここで見つけることができます

于 2012-09-21T01:10:47.227 に答える