Виртуальные функции могут иметь параметры по умолчанию?

Если я объявляю базовый класс (или класс интерфейса) и указываю значение по умолчанию для одного или нескольких его параметров, должны ли производные классы указывать те же значения по умолчанию, а если нет, то какие значения по умолчанию будут проявляться в производных классах?

Addendum: меня также интересует, как это может быть обработано различными компиляторами и любым вводом "рекомендуемой" практики в этом сценарии.

6 ответов


виртуалы могут иметь значения по умолчанию. По умолчанию в базовом классе не наследуются производными классами.

по умолчанию используется -- т. е. базовый класс' или производный класс' -- определяется статическим типом, используемым для вызова функции. При вызове объекта базового класса, указателя или ссылки используется значение по умолчанию, указанное в базовом классе. И наоборот, если вы вызываете через объект производного класса, указатель или ссылку на значения по умолчанию, обозначенные в производном классе не использовать. Ниже стандартного предложения приведен пример, демонстрирующий это.

некоторые компиляторы могут делать что-то другое, но это то, что стандарты C++03 и C++11 говорят:

(редактировать: стандарт C++11 говорит то же самое)

8.3.6.10:

виртуальный вызов функции (10.3) использует аргументы по умолчанию в объявление виртуальной функции определенный по статический тип указателя или ссылки, обозначающей объект. - переопределение функции в производном class не получает аргументы по умолчанию от функции it отменяет. [Пример:

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()
}
—end example]

редактировать вот пример программы, чтобы продемонстрировать, какие значения по умолчанию подобраны. Я использую С, а не classes просто для краткости -- class и struct точно такие же почти во всех отношениях, кроме default видимость.

#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

Это была тема одной из ранних работ Херба Саттера гуру недели сообщения.

более подробно, да, вы можете указать различные параметры по умолчанию. Они не будут работать так же, как виртуальные функции. Виртуальная функция вызывается для динамического типа объекта, а значения параметров по умолчанию основаны на статическом типе.

дано

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


Как вы можете видеть из других ответов, это сложная тема. Вместо того, чтобы пытаться сделать это или понять, что он делает (если вам нужно спросить сейчас, сопровождающий должен будет спросить или посмотреть его через год).

вместо этого создайте общедоступную не виртуальную функцию в базовом классе с параметрами по умолчанию. Затем он вызывает закрытую или защищенную виртуальную функцию, которая не имеет параметров по умолчанию и при необходимости переопределяется в дочерних классах. Тогда тебе не о чем беспокоиться. особенности того, как это будет работать, и код очень очевидны.


Это тот, который вы, вероятно, можете понять достаточно хорошо путем тестирования (т. е. это достаточно распространенная часть языка, что большинство компиляторов почти наверняка получают его правильно, и если вы не видите различий между компиляторами, их вывод можно считать довольно авторитетным).

#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;
}

это плохая идея, потому что аргументы по умолчанию, которые вы получите, будут зависеть от static тип объекта, тогда как 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);

Как и другие ответы, его плохая идея. Однако, поскольку никто не упоминает простое и эффективное решение, Вот оно: преобразуйте свои параметры в struct, а затем вы можете иметь значения по умолчанию для членов struct!

Так что вместо

//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)