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

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

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

Ответ 1

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

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

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

( EDIT: стандарт С++ 11 говорит точно то же самое)

8.3.6.10:

Виртуальный вызов функции (10.3) использует аргументы по умолчанию в объявление виртуальной функции определенный     по статическому типу указателя или ссылки, обозначающей объект. переопределяющая функция в производном     класс не получает аргументы по умолчанию из функции 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]

Изменить. Ниже приведен пример программы для демонстрации того, какие значения по умолчанию выбраны. Я использую struct здесь, а не class es просто для краткости - class и struct абсолютно одинаковы практически везде, кроме видимости по умолчанию.

#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

Ответ 2

Это была тема одного из ранних Guru of the Week.

Первое, что он говорит по этому поводу, НЕ ДЕЛАЙТЕ ЭТО.

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

Учитывая

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

Ответ 3

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

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

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

Ответ 5

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

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

Ответ 6

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

Итак, вместо <

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