web-dev-qa-db-ja.com

基本クラスから派生クラス関数を呼び出す

_class base
{
  public:
  virtual void start();
  virtual void stop();

  void doSomething() { start(); .... stop(); }
}

class derived : public base
{
  public:
   void start();
   void stop();
}
_

しかし、派生クラスでdoSomething()を呼び出すと、独自のStart()およびStop()の定義が使用されます-派生クラスではありません。

派生クラスのdoSomething()は、基本クラスと同一であるため、書き換えたくありません。何が悪いのですか?

それが明確でない場合は申し訳ありません。
派生クラスのStart()とStop()の動作は異なります(マシンは異なります)-変更されていないため、元の基本クラスのdoSomething()を使用します。新しい派生クラスコードを使用して、start()およびstop()を実行するだけです。

17
cpp help

あなたが投稿したコードはあなたが望むように動くはずです。 doSomethingのインスタンスでderivedを呼び出すと、startで定義されているオーバーライドされたstopおよびderived関数が呼び出されます。

ただし、例外があります。 doSomethingのコンストラクタまたはデストラクタで(直接または間接的に)baseを呼び出すと、呼び出されるstartおよびstopのバージョンはbaseで定義されたもの。そのような状況では、実際には有効なderivedインスタンスがまだないためです。完全に構築されていないか、部分的に破壊されているため、この言語では、部分オブジェクトを使用するメソッドを呼び出すことができません。

baseコンストラクタまたはデストラクタから呼び出さない場合は、ここに示されているもの以外にも問題があります。

27
Rob Kennedy

更新
doSomething()が派生クラスのバージョンのstart()およびstop()を呼び出そうとしているという以下のコメントに基づいて、質問に対する私の更新された回答は次のとおりです。

BaseとDerivedを定義した方法に問題はありません。おそらく「コードスライシング」と呼ばれるものを経験しているでしょう。宣言された型が「Base *」や「Base&」ではなく「Base」であるオブジェクトで「doSomething()」を呼び出しているため、オブジェクトがタイプBaseに変換されます。

悪い例:

 Derived derived;
 Base base = derived;
 base.doSomething();  // This is Base's version of doSomething()

良い例え:

 Base* base = new Derived;  // NOTE: declared type is "Base*"
 base->doSomething();  // This will call Derived version
 delete base;

補足:私の例のように直接ポインターを使用する代わりに、scoped_ptr、shared_ptr、unique_ptr、またはその他のスマートポインタークラスを使用する必要があります。ただし、問題を曖昧にしないために、この例では生のポインターを使用することにしました。 「スライス」の詳細については、以下を参照してください。

元のソリューション
次のようなことができます:

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

        virtual void start() {
           startInternal();
        }

        virtual void stop() {
            stopInternal();
        }

        void doSomething() {
            startInternal();
            // ...
            stopInternal();
        }
    private:
        void startInternal() {
          // ...
        } 
        void stopInternal() {
          // ...
        }
};

class Derived : public Base {
    public:
        Derived() {}
        virtual ~Derived() {}
        virtual void start() {
            // ...
        }
        virtual void stop() {
            // ...
        }
};

これを行うと、doSomething()はオーバーライドされない内部バージョンの開始/停止を使用します。このパターンは、コンストラクター/デストラクターが仮想メソッドとロジックを共有する必要がある場合に多く見られます。

また、当面の問題とは関係なく、仮想メソッドを持つクラスを作成するときは常に仮想デストラクタを作成する必要があることを忘れないでください。