36 votes

Des objets polymorphes sur la pile ?

En Pourquoi n'y a-t-il pas de classe de base en C++ ? , J'ai cité Stroustrup sur les raisons pour lesquelles une classe d'objets commune à toutes les classes est problématique en C++. Dans cette citation il y a la déclaration :

L'utilisation d'une classe de base universelle implique un coût : Les objets doivent être alloués au tas pour être polymorphes ;

Je n'y ai pas vraiment regardé à deux fois, et comme son sur la page d'accueil de Bjarnes Je suppose que beaucoup d'yeux ont parcouru cette phrase et ont signalé toute inexactitude.

Un commentateur a toutefois fait remarquer que ce n'était probablement pas le cas et, rétrospectivement, je ne trouve aucune raison valable pour que ce soit le cas. Un court scénario de test donne le résultat attendu de VDerived::f() .

struct VBase {
    virtual void f() { std::cout <<"VBase::f()\n"; }
};

struct VDerived: VBase {
    void f() { std::cout << "VDerived::f()\n"; }
};

void test(VBase& obj) {
    obj.f();
}

int main() {
    VDerived obj;
    test(obj);
}

Bien sûr, si l'argument formel à tester était test(VBase obj) le cas serait totalement différent, mais il ne s'agirait pas d'un argument pile vs. tas, mais plutôt d'une sémantique de copie.

Est-ce que Bjarne a tout faux ou est-ce que je rate quelque chose ?

0 votes

Il a été suggéré qu'il n'y a pas besoin d'une vtable dans cet exemple. Il est trivial d'ajouter une classe VDerived2, de construire dans main, d'appeler test, avec le résultat attendu comme suggéré par la question, donc une vtable doit être présente.

0 votes

Une conséquence amusante : vous n'avez pas écrit un destructeur virtuel, mais ici c'est parfaitement correct parce que ce n'est pas requis. Pourtant, la plupart des compilateurs (avec les avertissements activés) vous donneront un coup sur la tête...

0 votes

@Matthieu g++ -Wall(4.4.3) est poliment calme. Bien sûr, les destructeurs, virtuels ou non, n'ajouteraient rien à mon exemple et le code non pertinent est toujours, eh bien, non pertinent.

16voto

Lightness Races in Orbit Points 122793

Ça ressemble à du polymorphisme pour moi.

Le polymorphisme en C++ fonctionne lorsque vous avez indirection c'est-à-dire soit un pointer-to-T ou un reference-to-T . Où T est stocké n'est absolument pas pertinent.

Bjarne fait également l'erreur de dire "alloué au tas", ce qui est techniquement inexact.

(Remarque : cela ne signifie pas qu'une classe de base universelle est "bonne").

0 votes

Edit : Nevermind, je viens de relire l'exemple ci-dessus. Je ne suis plus sûr.

2 votes

@John : Conneries. C'est un appel à travers VBase& . Voici un exemple qui montre que votre affirmation est fausse. [edit : lol, ok]

1 votes

@Downvoters : Veuillez laisser un commentaire expliquant pourquoi, car vous avez tort.

7voto

Felix Dombek Points 2130

Je pense que Bjarne veut dire que obj ou plus précisément l'objet vers lequel il pointe, ne peut pas facilement être basé sur la pile dans ce code :

int f(int arg) 
{ 
    std::unique_ptr<Base> obj;    
    switch (arg) 
    { 
    case 1:  obj = std::make_unique<Derived1      >(); break; 
    case 2:  obj = std::make_unique<Derived2      >(); break; 
    default: obj = std::make_unique<DerivedDefault>(); break; 
    } 
    return obj->GetValue(); 
}

Il est impossible d'avoir un objet sur la pile qui change de classe ou qui n'est pas sûr de la classe exacte à laquelle il appartient.

(Bien sûr, pour être vraiment pédant, on pourrait allouer l'objet sur la pile en utilisant le placement-new sur un objet alloca -l'espace alloué. Le fait qu'il existe des solutions de contournement compliquées n'est pas le sujet ici, cependant).

Le code suivant ne fonctionne pas non plus comme on pourrait s'y attendre :

int f(int arg) 
{ 
    Base obj = DerivedFactory(arg); // copy (return by value)
    return obj.GetValue();
}

Ce code contient un découpage d'objets erreur : L'espace de la pile pour obj est seulement aussi grande qu'une instance de la classe Base ; lorsque DerivedFactory renvoie un objet d'une classe dérivée qui possède des membres supplémentaires, ceux-ci ne seront pas copiés dans l'objet de la classe dérivée. obj ce qui rend obj invalide et inutilisable en tant qu'objet dérivé (et peut-être même inutilisable en tant qu'objet de base).

En résumé, il existe une catégorie de comportements polymorphes qui ne peuvent pas être réalisés de manière directe avec des objets de pile.


Bien sûr, tout objet dérivé complètement construit, où qu'il soit stocké, peut agir comme un objet de base, et donc agir de manière polymorphe. Cela découle simplement de la est un relation que les objets des classes héritées ont avec leur classe de base.

0 votes

obj es "stack-based" : c'est un pointeur sur la pile ! "On ne peut pas avoir un objet sur la pile qui change de classe, ou qui est initialement incertain de la classe exacte à laquelle il appartient." Ni vous pouvez avoir un tel objet sur le tas ! Les pointeurs et les références sont capables de polymorphisme. Les objets sous-jacents ne le sont pas (et ne souffrent pas de crises d'identité). L'emplacement et la durée de stockage du ptr/ref ou de l'objet ne signifient rien. Le stockage dynamique dans votre exemple ne fait que faciliter l'adressage et prolonger la durée de vie. Je pourrais tout aussi bien allouer en pile les 3 éléments suivants Derived et écrire une fonction/ternaire pour assigner une de leurs adresses à l'adresse suivante obj .

0 votes

Chaque fois que j'ai besoin du genre de polymorphie que DerivedFactory prévoit, j'utilise des objets heap même si une simple copie par valeur vers un objet de durée de stockage "automatique" suffirait totalement s'il n'y avait pas cette polymorphie. Vous dites "tout aussi facilement", mais je doute sérieusement que vous puissiez fournir une bonne solution pour cela. N'hésitez pas à me prouver que j'ai tort, bien sûr.

0 votes

"Copie par valeur" ? Non nécessaire. Le polymorphisme est orthogonal aux durées de stockage de l'arbitre et du référent. Il exige seulement que vous utilisiez une référence/pointeur comme route. Cela peut être automatique et/ou faire référence à un objet automatique. Exemple : struct FactoryWarehouse { Derived1 d1; Derived2 d2; DerivedDefault dd; Base *get_pointer_to_some_derived(unsigned const index) { switch (index) { case 1: return &d1; case 2: return &d2; default: return &dd; } } }; Bien sûr, c'est un peu artificiel, mais vous pouvez utiliser le retour Base * polymorphe, montrant que, encore une fois, toute cette histoire de durée de stockage est un leurre.

3voto

Jan Hudec Points 27417

Après l'avoir lu, je pense que l'idée est (surtout au vu de la deuxième phrase sur la sémantique de la copie) que la classe de base universelle est inutile pour les objets manipulés par valeur, ce qui conduirait naturellement à plus de manipulation par référence et donc à plus de frais d'allocation de mémoire (pensez au vecteur de modèle par rapport au vecteur de pointeurs).

Donc je pense qu'il voulait dire que les objets devraient être alloués séparément de toute structure les contenant et que cela aurait conduit à beaucoup plus d'allocations sur le tas. Telle qu'elle est écrite, l'affirmation est en effet fausse.

PS (ad commentaire du capitaine Girafe) : Il serait en effet inutile d'avoir une fonction

f(object o)

ce qui signifie que la fonction générique devrait être

f(object &o)

Et cela signifierait que l'objet devrait être polymorphe, ce qui signifie qu'il devrait être alloué séparément, ce qui souvent signifie sur le tas, bien qu'il peut être sur la pile. D'autre part, maintenant vous avez :

template <typename T>
f(T o) // see, no reference

qui finit par être plus efficace dans la plupart des cas. C'est particulièrement le cas des collections, où si tout ce que vous aviez était un vecteur de tels objets de base (comme le fait Java), vous devriez allouer tous les objets séparément. Ce qui représenterait une surcharge importante, surtout si l'on tient compte des mauvaises performances des allocateurs au moment de la création du C++ (Java a toujours un avantage sur ce point, car la copie du garbage collector est plus efficace et le C++ ne peut pas en utiliser).

1 votes

Pouvez-vous nous en dire plus ? Il dit clairement "Les objets doivent être alloués au tas pour être polymorphes" mais ce n'est pas vrai. A tout le moins, il peut avoir un point (que j'aimerais comprendre) mais son affirmation ne semble pas être vraie dans tous les cas.

0 votes

Jan : Ce serait certainement une mauvaise chose à faire, mais l'affirmation de Bjarne quant à pourquoi est incorrect.

1 votes

@John : En effet, son affirmation comme écrit a tort. J'essayais de trouver quelle était la logique menant à cette déclaration incorrecte.

2voto

Byron Points 371

L'affirmation de Bjarne n'est pas correcte.

Les objets, c'est-à-dire les instances d'une classe, deviennent potentiellement polymorphes en ajoutant au moins une méthode virtuelle à leur déclaration de classe. Les méthodes virtuelles ajoutent un niveau d'indirection, permettant à un appel d'être redirigé vers l'implémentation réelle qui peut ne pas être connue de l'appelant.

Pour cela, peu importe que l'instance soit allouée au tas ou à la pile, tant qu'elle est accessible par une référence ou un pointeur ( T& instance o T* instance ).

Une raison possible pour laquelle cette affirmation générale s'est glissée sur la page web de Bjarne pourrait être qu'il est néanmoins extrêmement courant d'allouer au tas des instances au comportement polymorphe. Ceci est principalement dû au fait que l'implémentation réelle n'est pas connue de l'appelant qui l'a obtenue par le biais d'une fonction de fabrique quelconque.

1 votes

Le fait que l'implémentation ne soit pas connue de l'appelant rend l'assertion de tout méthode d'attribution en quelque sorte encore plus de bananes... pas moins.

1voto

John Chadwick Points 1903

Je pense qu'il voulait dire qu'il n'était pas possible de le stocker dans une variable de type base. Vous avez raison de dire que vous pouvez le stocker sur la pile s'il est de type dérivé, car il n'y a rien de spécial à cela ; conceptuellement, il s'agit simplement de stocker les données de la classe et de ses dérivés + une vtable.

edit : Okay, maintenant je suis confus, en regardant à nouveau l'exemple. On dirait que tu as peut-être raison maintenant...

0 votes

Pas exactement. J'étais d'accord pour dire que ce qu'il faisait était légal, mais j'ai complètement passé sous silence l'utilisation de références qui annulaient en gros l'affirmation de Bjarne. J'étais d'accord pour dire que ce qu'il faisait était correct, mais je n'étais pas d'accord pour dire que c'était un cas de polymorphisme, ce qui était le cas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X