Czy funkcje wirtualne mogą mieć domyślne parametry?

Jeśli deklaruję klasę bazową (lub klasę interfejsu) i określam domyślną wartość dla jednego lub więcej jej parametrów, czy klasy pochodne muszą określać te same wartości domyślne, a jeśli nie, jakie wartości domyślne pojawią się w klasach pochodnych?

Dodatek: interesuje mnie również, jak to może być obsługiwane przez różne kompilatory i wszelkie informacje na temat" zalecanej " praktyki w tym scenariuszu.

Author: John Dibling, 2010-08-20

6 answers

Wirtualne mogą mieć domyślne wartości. Wartości domyślne w klasie bazowej nie są dziedziczone przez klasy pochodne.

Które domyślnie jest używane -- tzn. klasa bazowa 'lub klasa pochodna' -- jest określona przez typ statyczny używany do wywołania funkcji. Jeśli wywołujesz obiekt klasy bazowej, wskaźnik lub referencję, używana jest wartość domyślna oznaczona w klasie bazowej. I odwrotnie, jeśli wywołujesz obiekt klasy pochodnej, używany jest wskaźnik lub odniesienie do wartości domyślnych oznaczonych w klasie pochodnej. Poniżej standardowego notowania znajduje się przykład, który to pokazuje.

Niektóre Kompilatory mogą robić coś innego, ale tak mówią standardy C++03 I C++11:

(EDIT : Standard C++11 mówi dokładnie to samo)

8.3.6.10:

Wirtualne wywołanie funkcji (10.3) wykorzystuje domyślne argumenty w deklaracja funkcji wirtualnej zdecydowana przez statyczny typ wskaźnika lub odniesienia oznaczającego obiekt. Na funkcja nadrzędna w pochodnej klasa nie pobiera domyślnych argumentów z funkcji, którą / align = "left" / [Przykład:

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 Oto przykładowy program, który zademonstruje, jakie wartości domyślne są pobierane. Używam structs zamiast class es po prostu dla zwięzłości -- class i struct są dokładnie takie same pod każdym względem, z wyjątkiem domyślnej widoczności.

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

Wyjście tego programu (na MSVC10 i GCC 4.4) jest:

Base 42
Der 42
Der 84
 175
Author: John Dibling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-27 15:41:36

To był temat jednego z pierwszych postów Herba Suttera "Guru tygodnia".

Pierwsze, co mówi na ten temat, to nie rób tego.

Bardziej szczegółowo, tak, można określić różne domyślne parametry. Nie będą działać tak samo jak funkcje wirtualne. Funkcja wirtualna jest wywoływana na typie dynamicznym obiektu, podczas gdy domyślne wartości parametrów są oparte na typie statycznym.

Given

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

Powinieneś Odp:: foo1 B:: foo2 B:: foo1

 29
Author: David Thornley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-18 01:12:50

Jak widać z innych odpowiedzi jest to skomplikowany temat. Zamiast próbować to zrobić lub zrozumieć, co to robi (jeśli musisz zapytać teraz, opiekun będzie musiał zapytać lub sprawdzić to za rok).

Zamiast tego utwórz publiczną nie wirtualną funkcję w klasie bazowej z domyślnymi parametrami. Następnie wywołuje prywatną lub chronioną funkcję wirtualną, która nie ma domyślnych parametrów i jest zastępowana w klasach potomnych w razie potrzeby. Wtedy nie musisz się martwić o szczegóły jak to będzie działać i Kod jest bardzo oczywiste.

 3
Author: Mark B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-20 18:22:45

Jest to jeden, który prawdopodobnie można dowiedzieć się dość dobrze przez testowanie (tj., jest to wystarczająco główny element języka, że większość kompilatorów prawie na pewno dobrze i jeśli nie widzisz różnic między kompilatorami, ich wyjście może być uznane za dość dobrze autorytatywne).

#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
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-20 18:33:04

Jest to zły pomysł, ponieważ domyślne argumenty będą zależeć od typu static obiektu, podczas gdy funkcja virtual będzie zależeć od typu dynamic .

To znaczy, gdy wywołujesz funkcję z domyślnymi argumentami, domyślne argumenty są zastępowane podczas kompilacji, niezależnie od tego, czy funkcja jest virtual, czy nie.

@cppcoder zaproponował następujący przykład w swoim [closed] pytanie :

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

, która daje następujące wyjście:

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

Z pomocą powyższego wyjaśnienia łatwo zrozumieć, dlaczego. W czasie kompilacji kompilator zastępuje domyślne argumenty z funkcji składowych statycznych typów wskaźników, czyniąc jego main funkcją równoważną następującej:

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

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

    B* bb = new B();
    bb->display(9);
 2
Author: Oktalist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:11

Jak Inne odpowiedzi mają szczegółowe, to zły pomysł. Ponieważ jednak nikt nie wspomina o prostym i skutecznym rozwiązaniu, oto jest: przekonwertuj swoje parametry na struct i wtedy możesz mieć domyślne wartości na struct members!

Więc zamiast

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

Zrób to,

//good idea
struct Param1 {
  int x = 0, y = 0, z = 0;
};
virtual method1(const Param1& p)
 1
Author: ShitalShah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-05 02:51:04