Les fonctions virtuelles peuvent-elles avoir des paramètres par défaut?

Si je déclare une classe de base (ou une classe d'interface) et spécifie une valeur par défaut pour un ou plusieurs de ses paramètres, les classes dérivées doivent-elles spécifier les mêmes valeurs par défaut et sinon, quelles valeurs par défaut se manifesteront dans les classes dérivées?

Addendum: je suis également intéressé par la façon dont cela peut être géré entre différents compilateurs et toute entrée sur la pratique "recommandée" dans ce scénario.

130
demandé sur John Dibling 2010-08-20 22:04:25

6 réponses

Les Virtuals peuvent avoir des valeurs par défaut. Les valeurs par défaut de la classe de base ne sont pas héritées par les classes dérivées.

Quelle valeur par défaut est utilisée - c'est-à-dire la classe de base' ou une classe dérivée' - est déterminée par le type statique utilisé pour effectuer l'appel à la fonction. Si vous appelez via un objet de classe de base, un pointeur ou une référence, la valeur par défaut indiquée dans la classe de base est utilisée. Inversement, si vous appelez via un objet de classe dérivé, un pointeur ou une référence, les valeurs par défaut indiquées dans la classe dérivée sont utilisées. Il y a un exemple ci-dessous la citation Standard qui le démontre.

Certains compilateurs peuvent faire quelque chose de différent, mais c'est ce que disent les normes C++03 et C++11:

(EDIT : la norme C++11 dit exactement la même chose)

8.3.6.10:

Un appel de fonction virtuelle (10.3) utilise les arguments par défaut dans le déclaration de la fonction virtuelle déterminer par le type statique du pointeur ou de la référence indiquant le objet. Un fonction de substitution dans un dérivé la classe n'acquiert pas d'arguments par défaut de la fonction remplacer. [Exemple:

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 Voici un exemple de programme pour démontrer les valeurs par défaut. J'utilise struct S ici plutôt que class ES simplement par souci de concision - class et struct sont exactement les mêmes dans presque tous les sens sauf la visibilité par défaut.

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

La sortie de ce programme (sur MSVC10 et GCC 4.4) est:

Base 42
Der 42
Der 84
175
répondu John Dibling 2013-08-27 15:41:36

C'était le sujet de L'un des premiers messages de Herb Sutter gourou de la semaine.

La première chose qu'il dit sur le sujet est de ne pas faire ça.

Plus En détail, oui, vous pouvez spécifier différents paramètres par défaut. Ils ne fonctionneront pas de la même manière que les fonctions virtuelles. Une fonction virtuelle est appelée Sur le type dynamique de l'objet, tandis que les valeurs des paramètres par défaut sont basées sur le type statique.

Donné

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

Vous devriez obtenir A::toto1 B:: foo2 B::toto1

29
répondu David Thornley 2016-02-18 01:12:50

Comme vous pouvez le voir dans les autres réponses, c'est un sujet compliqué. Au lieu d'essayer de le faire ou de comprendre ce qu'il fait (si vous devez demander maintenant, le mainteneur devra le demander ou le rechercher dans un an).

À la place, créez une fonction publique non virtuelle dans la classe de base avec les paramètres par défaut. Ensuite, il appelle une fonction virtuelle privée ou protégée qui n'a pas de paramètres par défaut et est remplacée dans les classes enfants si nécessaire. Alors vous n'avez pas à vous soucier de la détails de la façon dont cela fonctionnerait et le code est très évident.

3
répondu Mark B 2010-08-20 18:22:45

C'est celui que vous pouvez probablement comprendre raisonnablement bien en testant (c'est-à-dire que c'est une partie suffisamment courante du langage que la plupart des compilateurs obtiennent presque certainement et à moins que vous ne voyiez des différences entre les compilateurs, leur sortie peut être considérée comme assez bien autoritaire).

#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
répondu Jerry Coffin 2010-08-20 18:33:04

C'est une mauvaise idée, car les arguments par défaut que vous obtenez dépendront du type static de l'objet, alors que la fonction virtual envoyée dépendra du type dynamic.

C'est-à-dire que lorsque vous appelez une fonction avec des arguments par défaut, les arguments par défaut sont substitués au moment de la compilation, que la fonction soit virtual ou non.

@cppcoder a offert l'exemple suivant dans son [fermé] question:

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

, Qui produit la sortie suivante:

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

, Avec l'aide de l'explication ci-dessus, il est facile de voir pourquoi. Au moment de la compilation, le compilateur remplace les arguments par défaut des fonctions membres des types statiques des pointeurs, rendant sa fonction main équivalente à la suivante:

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

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

    B* bb = new B();
    bb->display(9);
2
répondu Oktalist 2017-05-23 11:47:11

Comme d'autres réponses l'ont détaillé, c'est une mauvaise idée. Cependant, puisque personne ne mentionne une solution simple et efficace, Le voici: Convertissez vos paramètres en struct et vous pouvez avoir des valeurs par défaut en struct members!

Donc au lieu de,

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

Faites ceci,

//good idea
struct Param1 {
  int x = 0, y = 0, z = 0;
};
virtual method1(const Param1& p)
1
répondu ShitalShah 2017-08-05 02:51:04