44

これは安全ですか?

class Derived:  public PublicBase, private PrivateBase
{
 ... 

   ~Derived()
   {
      FunctionCall();
   }

   virtual void FunctionCall()
   {
      PrivateBase::FunctionCall();
   }
}

class PublicBase
{
   virtual ~PublicBase(){};
   virtual void FunctionCall() = 0;
}

class PrivateBase
{
   virtual ~PrivateBase(){};
   virtual void FunctionCall()
   {
    ....
   }
}


PublicBase* ptrBase = new Derived();
delete ptrBase;

このコードは、IPが不正なアドレスに含まれていると折り目が付くことがあります。

コンストラクターで仮想関数を呼び出すことは、誰にとっても明らかなことです。

http://www.artima.com/cppsource/nevercall.htmlのような記事から、デストラクタも仮想関数を呼び出すのにあまり適した場所ではないことを理解しています。

私の質問は「これは本当ですか?」です。VS2010とVS2005でテストし、PrivateBase::FunctionCallが呼び出されました。未定義動作ですか?

4

5 に答える 5

76

ここでの流れに逆らいます...しかし、最初に、PublicBaseデストラクタが仮想であると想定する必要があります。そうしないと、Derivedデストラクタが呼び出されることはありません。

通常、コンストラクタ/デストラクタから仮想関数を呼び出すことはお勧めできません。

この理由は、これら2つの操作中の動的ディスパッチが奇妙であるためです。オブジェクトの実際のタイプは構築中に変更され、破棄中に再び変更されます。デストラクタが実行されているとき、オブジェクトはまさにそのタイプであり、それから派生したタイプになることはありません。動的ディスパッチは常に有効ですが、仮想関数の最終的なオーバーライドは、階層内のどこにいるかによって変わります。

つまり、コンストラクタ/デストラクタ内の仮想関数の呼び出しが、実行されているコンストラクタ/デストラクタのタイプから派生したタイプで実行されることを期待してはなりません。

だが

特定のケースでは、最終的なオーバーライド(少なくとも階層のこの部分)はレベルより上にあります。さらに、動的ディスパッチをまったく使用していません。呼び出しPrivateBase::FunctionCall();は静的に解決され、事実上、非仮想関数の呼び出しと同等です。関数が仮想であるかどうかという事実は、この呼び出しには影響しません。

ですから、そうです、あなたがしているようにそれは問題ありませんが、ほとんどの人がルールの理由ではなくルールのマントラを学ぶので、コードレビューでこれを説明することを余儀なくされます。

于 2012-08-23T13:53:44.007 に答える
26

これは安全ですか?

はい。コンストラクタまたはデストラクタから仮想関数を呼び出すと、オブジェクトの動的型が現在構築または破棄されているものであるかのように関数がディスパッチされます。この場合、のデストラクタから呼び出されるDerivedため、にディスパッチされます(この場合、非仮想的Derived::FunctionCallに呼び出されます)。PrivateBase::FunctionCallこれらはすべて明確に定義されています。

次の3つの理由から、コンストラクタまたはデストラクタから仮想関数を呼び出すことは「お勧めできません」。

  • 基本クラスから呼び出し、(誤って)派生クラスのオーバーライドにディスパッチされることを期待すると、予期しない動作が発生します。
  • 純粋な仮想の場合、未定義の動作が発生します。
  • あなたはそれが常に間違っていると信じている人々にあなたの決定を説明しなければならないでしょう。
于 2012-08-23T14:42:39.807 に答える
3

一般に、仮想関数を呼び出すことは、それがディスパッチされる可能性のあるクラスのオブジェクト(つまり、最も派生したクラスの「完全な」オブジェクト)が完全に構築されていない限り、お勧めできません。そして、これはそうではありません

  • すべてのコンストラクターが実行を終了するまで
  • デストラクタが実行を終了した後
于 2012-08-23T13:42:53.620 に答える
1

スコットによると、それは非常に悪い考えです:リンク

これは私がコンパイルして実行したものであり、破壊プロセスをよりよく理解するのに役立ちます。

#include <iostream>
using namespace std;


class A {
public:
  virtual void method() {
    cout << "A::method" << endl;
  }

  void otherMethod() {
    method();
  }

  virtual ~A() {
    cout << "A::destructor" << endl;
    otherMethod();
  }

};

class B : public A {
public:
  virtual void method() {
    cout << "B::method" << endl;
  }

  virtual ~B() {
    cout << "B::destructor" << endl;
  }
};

int main() {

  A* a = new B();

  a->method();

  delete a;

}
于 2012-08-23T13:44:20.910 に答える
0

これは安全ですか?

はいといいえ。

はい、そのままの例は明確に定義されており、機能するためです。そのすべては他の答えによってよく説明されています。また、このコードは、記述された方法でコンパイルされないため、完全に安全です。基本クラスのプライベートdtorなどです。

この例のように安全ではない理由は、このコードは、他の誰もクラスFunctionCallからオーバーライドしないことを前提としており、オブジェクトの破棄時にそのオーバーライドが呼び出されることを期待しないためです。ほとんどの場合、コンパイラはこれについて不平を言うでしょう。をfinalとしてマークすることで、コードを改善できます。DerivedFunctionCall

class Derived : public PublicBase, private PrivateBase
{
 ... 
   virtual void FunctionCall() final;
}

または最終Derivedとしてあなたのクラス:

class Derived final : public PublicBase, private PrivateBase
{
 ... 
   virtual void FunctionCall();
}

古いコンパイラでコンパイルする場合、またはその他の理由でc ++ 11を使用できない場合は、少なくともここでより明確にし、クラスFunctionCallの子孫によってオーバーライドされているかどうかに関係なく、実行時に何が起こるかを正確にコードに記述できます。Derived

class Derived : public PublicBase, private PrivateBase
{
 ... 
   ~Derived()
   {
      Derived::FunctionCall(); // be explicit which FunctionCall will be called
   }

   virtual void FunctionCall()
   {
      PrivateBase::FunctionCall();
   }
}
于 2019-10-02T04:48:31.340 に答える