仮想関数にデフォルトのパラメーターを設定できますか?


164

基本クラス(またはインターフェイスクラス)を宣言し、そのパラメーターの1つ以上にデフォルト値を指定する場合、派生クラスは同じデフォルトを指定する必要がありますか?そうでない場合、どのデフォルトが派生クラスに現れますか?

補遺:私はまた、これがさまざまなコンパイラでどのように処理されるか、およびこのシナリオでの「推奨される」実践に関する入力に興味があります。


1
これは簡単にテストできることのようです。試しましたか?
そして、

22
私はそれを試していますが、動作がどのように「定義される」かについての具体的な情報が見つからないので、最終的には特定のコンパイラの答えを見つけますが、すべてのコンパイラが同じことをするかどうかはわかりません事。私もおすすめの練習に興味があります。
アーノルドスペンス

1
動作は明確に定義されており、間違っているコンパイラを見つけることはないでしょう(おそらく、gcc 1.xやVC ++ 1.0などをテストした場合)。推奨される方法は、これを行わないことです。
Jerry Coffin

回答:


212

仮想にはデフォルトがある場合があります。基本クラスのデフォルトは、派生クラスによって継承されません。

どのデフォルトが使用されるか(つまり、基本クラスまたは派生クラス)は、関数の呼び出しに使用される静的型によって決まります。基本クラスのオブジェクト、ポインター、または参照を介して呼び出す場合、基本クラスで示されているデフォルトが使用されます。逆に、派生クラスオブジェクト、ポインター、または参照を介して呼び出す場合は、派生クラスで示されるデフォルトが使用されます。これを示す標準的な引用の下に例があります。

一部のコンパイラは異なる動作をする場合がありますが、これはC ++ 03およびC ++ 11標準が言うとおりです。

8.3.6.10:

仮想関数呼び出し(10.3)は、オブジェクトを示すポインターまたは参照の静的型によって決定される仮想関数の宣言で、デフォルトの引数を使用します。派生クラスのオーバーライド関数は、オーバーライドする関数からデフォルトの引数を取得しません。例:

struct A {
  virtual void f(int a = 7);
};
struct B : public A {
  void f(int a);
};
void m()
{
  B* pb = new B;
  A* pa = pb;
  pa->f(); //OK, calls pa->B::f(7)
  pb->f(); //error: wrong number of arguments for B::f()
}

以下は、どのデフォルトが選択されるかを示すサンプルプログラムです。私が使用しているstructのではなく、ここでSをclass単に簡潔にするために、ES - classstruct、デフォルトの可視性を除いて、ほぼすべての方法でまったく同じです。

#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>

using std::stringstream;
using std::string;
using std::cout;
using std::endl;

struct Base { virtual string Speak(int n = 42); };
struct Der : public Base { string Speak(int n = 84); };

string Base::Speak(int n) 
{ 
    stringstream ss;
    ss << "Base " << n;
    return ss.str();
}

string Der::Speak(int n)
{
    stringstream ss;
    ss << "Der " << n;
    return ss.str();
}

int main()
{
    Base b1;
    Der d1;

    Base *pb1 = &b1, *pb2 = &d1;
    Der *pd1 = &d1;
    cout << pb1->Speak() << "\n"    // Base 42
        << pb2->Speak() << "\n"     // Der 42
        << pd1->Speak() << "\n"     // Der 84
        << endl;
}

このプログラムの出力(MSVC10およびGCC 4.4)は次のとおりです。

Base 42
Der 42
Der 84

リファレンスをありがとう、それは私がコンパイラ全体で合理的に期待できる動作を教えてくれます(私は願っています)。
アーノルドスペンス

これは私の以前の要約の修正です:私は参照のためにこの回答を受け入れ、集合的な推奨事項は、祖先で以前に指定されたデフォルトのパラメーターを変更しない限り、仮想関数にデフォルトのパラメーターがあってもよいということを述べますクラス。
アーノルドスペンス

gcc 4.8.1を使用していますが、「引数の数が間違っています」というコンパイルエラーが発生しません!!! バグを見つけるのに1日半
かかりました

2
しかし、その理由はありますか?静的型によって決定されるのはなぜですか?
user1289 2015

2
:不要な何かと課題そのことについての警告などの仮想メソッドのクラン・整頓扱いデフォルトパラメータgithub.com/llvm-mirror/clang-tools-extra/blob/master/clang-tidy/...
マーティンPecka

38

これは、Herb Sutterの初期のGuru of the Week投稿の1つのトピックでした。

彼がこの問題について最初に言うことは、それをしないでください。

より詳細には、はい、異なるデフォルトパラメータを指定できます。これらは、仮想関数と同じようには機能しません。仮想関数はオブジェクトの動的タイプで呼び出されますが、デフォルトのパラメーター値は静的タイプに基づいています。

与えられた

class A {
    virtual void foo(int i = 1) { cout << "A::foo" << i << endl; }
};
class B: public A {
    virtual void foo(int i = 2) { cout << "B::foo" << i << endl; }
};
void test() {
A a;
B b;
A* ap = &b;
a.foo();
b.foo();
ap->foo();
}

A :: foo1 B :: foo2 B :: foo1を取得する必要があります


7
ありがとう。Herb Sutterの「Do n't do that」には重みがあります。
アーノルドスペンス

2
@ArnoldSpence、実際にはHerb Sutterはこの推奨事項を超えています。彼は、インターフェイスに仮想メソッドを含めるべきではないと信じています:gotw.ca/publications/mill18.htm。メソッドが具体的でオーバーライドできない(すべきでない)場合は、デフォルトのパラメーターを指定しても安全です。
Mark Ransom 2013

1
彼が「やらないこと」が意味するのは「仮想メソッドにデフォルトのパラメーターを指定しない」ではなく、オーバーライドするメソッドの「デフォルトのパラメーターのデフォルト値を変更しない」であると考えています
Weipeng L

6

取得されるデフォルトの引数はオブジェクトの静的な型に依存するため、これは悪い考えです。virtualディスパッチされる関数は動的な型に依存します。

つまり、デフォルトの引数で関数を呼び出すと、関数がそうであるかどうかに関係なく、コンパイル時にデフォルトの引数が代入されますvirtual

@cppcoderは、[クローズド]の質問で次の例を提供しました。

struct A {
    virtual void display(int i = 5) { std::cout << "Base::" << i << "\n"; }
};
struct B : public A {
    virtual void display(int i = 9) override { std::cout << "Derived::" << i << "\n"; }
};

int main()
{
    A * a = new B();
    a->display();

    A* aa = new A();
    aa->display();

    B* bb = new B();
    bb->display();
}

次の出力が生成されます。

Derived::5
Base::5
Derived::9

上記の説明を参考にすれば、その理由が簡単にわかります。コンパイラーは、コンパイル時に、ポインターの静的型のメンバー関数からのデフォルト引数を代用し、main関数を以下と同等にします。

    A * a = new B();
    a->display(5);

    A* aa = new A();
    aa->display(5);

    B* bb = new B();
    bb->display(9);

4

他の答えからわかるように、これは複雑なテーマです。これを行うか、それが何をするかを理解しようとする代わりに(もしあなたが今尋ねなければならないなら、メンテナは今から1年それを尋ねるか調べる必要があります)。

代わりに、デフォルトのパラメーターを使用して、基本クラスにパブリック非仮想関数を作成します。次に、デフォルトのパラメーターを持たず、必要に応じて子クラスでオーバーライドされるプライベートまたは保護された仮想関数を呼び出します。そうすれば、それがどのように機能するかの詳細について心配する必要はなく、コードは非常に明白です。


1
それはまったく複雑ではありません。デフォルトのパラメーターは、名前解決とともに検出されます。彼らは同じルールに従います。
Edward Strange

4

これはおそらくテストによってかなり適切に理解できるものです(つまり、ほとんどのコンパイラーがほぼ間違いなく正しく理解する言語の十分に主流の部分であり、コンパイラー間の違いが見られない限り、それらの出力はかなり信頼できると見なすことができます)。

#include <iostream>

struct base { 
    virtual void x(int a=0) { std::cout << a; }
    virtual ~base() {}
};

struct derived1 : base { 
    void x(int a) { std:: cout << a; }
};

struct derived2 : base { 
    void x(int a = 1) { std::cout << a; }
};

int main() { 
    base *b[3];
    b[0] = new base;
    b[1] = new derived1;
    b[2] = new derived2;

    for (int i=0; i<3; i++) {
        b[i]->x();
        delete b[i];
    }

    derived1 d;
    // d.x();       // won't compile.
    derived2 d2;
    d2.x();
    return 0;
}

4
@GMan:[無邪気に注意深く見ている]何がリーク?:-)
ジェリーの棺

彼は仮想デストラクタの欠如に言及していると思います。しかし、この場合はリークしません。
John Dibling 2010

1
@Jerry、ベースクラスポインターを介して派生オブジェクトを削除する場合、デストラクターは仮想です。そうでない場合、基本クラスのデストラクターがそれらすべてに対して呼び出されます。これにはデストラクタがないので問題ありません。:-)
チャッパー

2
@ジョン:元々削除はありませんでした。私は仮想デストラクタの欠如を完全に無視しました。そして... @chappar:いいえ、それは大丈夫ではありません。それはしなければならない基本クラスを介して削除する仮想デストラクタを持っているか、未定義の動作を取得します。(このコードの動作は未定義です。)派生クラスが持つデータやデストラクタとは関係ありません。
GManNickG 2010

@Chappar:コードはもともと何も削除しませんでした。手元の質問とはほとんど関係ありませんが、基本クラスに仮想dtorを追加しました-些細なdtorを使用しても、ほとんど問題になりませんが、GManはそれがなければコードにUBがあるので完全に正しいです。
ジェリーコフィン

4

他の答えが詳しく述べているように、その悪い考え。ただし、簡単で効果的なソリューションについては誰も触れていないため、ここで説明します。パラメーターを構造体に変換すると、構造体のメンバーにデフォルト値を設定できます。

代わりに、

//bad idea
virtual method1(int x = 0, int y = 0, int z = 0)

これを行う、

//good idea
struct Param1 {
  int x = 0, y = 0, z = 0;
};
virtual method1(const Param1& p)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.