web-dev-qa-db-fra.com

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, dans la négative, 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é par différents compilateurs et par toute contribution sur les pratiques "recommandées" dans ce scénario.

148
Arnold Spence

Les virtuels 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.

La valeur par défaut 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 appeler 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. Un exemple sous la citation Standard en est une illustration.

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

( EDIT : Le standard 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 la déclaration de la fonction virtuelle déterminée par le type statique du pointeur ou de la référence désignant l'objet. Une fonction de substitution dans une classe dérivée n'acquiert pas les arguments par défaut de la fonction qu'elle substitue. [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 montrant quelles valeurs par défaut sont détectées. J'utilise structs ici plutôt que classes simplement par souci de concision - class et struct sont exactement les mêmes à presque tous les égards 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
192
John Dibling

C’était le sujet de l’un des premiers messages du gourou de la semaine de Herb Sutter.

La première chose qu'il dit à ce sujet est NE PAS FAIRE CELA.

Oui, plus en détail, 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 de paramètre 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 avoir A :: foo1 B :: foo2 B :: foo1

35
David Thornley

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

Autrement dit, lorsque vous appelez une fonction avec des arguments par défaut, les arguments par défaut sont remplacés au moment de la compilation, que la fonction soit virtual ou non.

@cppcoder a proposé l'exemple suivant dans son [closed] 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();
}

Ce qui produit la sortie suivante:

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

À l'aide de l'explication ci-dessus, il est facile de voir pourquoi. Lors de la compilation, le compilateur substitue les arguments par défaut des fonctions membres des types statiques des pointeurs, ce qui rend sa fonction main équivalente à celle-ci:

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

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

    B* bb = new B();
    bb->display(9);
4
Oktalist

C’est un test que vous pouvez probablement comprendre assez bien en le testant (c’est-à-dire qu’il s’agit d’une partie suffisamment dominante du langage pour que la plupart des compilateurs s’y fassent bien, et à moins que vous ne constatiez des différences entre les compilateurs, leur sortie peut être considérée comme faisant autorité).

#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;
}
4
Jerry Coffin

Comme vous pouvez le constater dans les autres réponses, il s’agit d’un sujet compliqué. Au lieu d'essayer de faire ceci ou de comprendre ce que ça fait (si vous devez demander maintenant, le responsable de la maintenance devra le demander ou le rechercher dans un an).

Au lieu de cela, créez une fonction publique non virtuelle dans la classe de base avec des paramètres par défaut. Ensuite, il appelle une fonction virtuelle privée ou protégée qui ne possède pas de paramètres par défaut et qui est remplacée dans les classes enfants en fonction des besoins. Ensuite, vous n'avez pas à vous soucier de la façon dont cela fonctionnerait et le code est très évident.

3
Mark B

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

Donc au lieu de

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

fais ça,

//good idea
struct Param1 {
  int x = 0, y = 0, z = 0;
};
virtual method1(const Param1& p)
2
Shital Shah