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


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

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

6 130

6 ответов:

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

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

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

(EDIT: стандарт C++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]

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

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

первое, что он говорит по этому поводу-не делай этого.

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

дано

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

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

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

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

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