Могут ли виртуальные функции иметь параметры по умолчанию? - PullRequest
143 голосов
/ 20 августа 2010

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

Приложение. Меня также интересует, как это можно обрабатывать в разных компиляторах, а также какие-либо сведения о «рекомендуемой» практике в этом сценарии.

Ответы [ 6 ]

188 голосов
/ 20 августа 2010

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

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

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

( РЕДАКТИРОВАТЬ : Стандарт 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()
}
—end example]

Редактировать Вот пример программы, демонстрирующей, какие значения по умолчанию выбраны. Я просто использую struct здесь, а не class просто для краткости - 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
34 голосов
/ 20 августа 2010

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

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

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

Учитывая

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

4 голосов
/ 20 августа 2010

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

#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;
}
3 голосов
/ 23 сентября 2014

Это плохая идея, потому что аргументы по умолчанию, которые вы получите, будут зависеть от статического типа объекта, тогда как отправляемая функция virtual будет зависеть от dynamic type.

То есть, когда вы вызываете функцию с аргументами по умолчанию, аргументы по умолчанию подставляются во время компиляции, независимо от того, является ли функция 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);
3 голосов
/ 20 августа 2010

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

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

1 голос
/ 05 августа 2017

Как подробно ответили другие ответы, это плохая идея. Однако, поскольку никто не упоминает о простом и эффективном решении, вот оно: Преобразуйте ваши параметры в 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)
...