L'optimisation de la base vide est excellente. Cependant, il est livré avec la restriction suivante:
L'optimisation de base vide est interdite si l'une des classes de base vide est également le type ou la base du type du premier membre de données non statique, car les deux sous-objets de base du même type doivent avoir des adresses différentes dans la représentation d'objet du type le plus dérivé.
Pour expliquer cette restriction, considérez le code suivant. Le static_assert
échouera. Alors que la modification de Foo
ou Bar
de hériter à la place Base2
évitera l'erreur:
#include <cstddef>
struct Base {};
struct Base2 {};
struct Foo : Base {};
struct Bar : Base {
Foo foo;
};
static_assert(offsetof(Bar,foo)==0,"Error!");
Je comprends parfaitement ce comportement. Ce que je ne comprends pas, c'est pourquoi ce comportement particulier existe . Il a évidemment été ajouté pour une raison, car il s'agit d'un ajout explicite et non d'un oubli. Quelle en est la raison?
En particulier, pourquoi les deux sous-objets de base devraient-ils avoir des adresses différentes? Dans ce qui précède, Bar
est un type et foo
est une variable membre de ce type. Je ne vois pas pourquoi la classe de base Bar
importe pour la classe de base du type de foo
, ou vice-versa.
En effet, je ne sais quoi, je m'attendrais à ce que ce &foo
soit la même que l'adresse de l' Bar
instance qui la contient - comme cela doit être dans d'autres situations (1) . Après tout, je ne fais rien d'extraordinaire avec l' virtual
héritage, les classes de base sont vides malgré tout, et la compilation avec Base2
montre que rien ne casse dans ce cas particulier.
Mais il est clair que ce raisonnement est incorrect d'une manière ou d'une autre, et il existe d'autres situations où cette limitation serait requise.
Disons que les réponses devraient être pour C ++ 11 ou plus récent (j'utilise actuellement C ++ 17).
(1) Remarque: EBO a été mis à niveau en C ++ 11, et en particulier est devenu obligatoire pour StandardLayoutType
s (bien que Bar
, ci-dessus, ce ne soit pas a StandardLayoutType
).
la source
Base *a = new Bar(); Base *b = a->foo;
aveca==b
, maisa
et ceb
sont clairement des objets différents (peut-être avec des remplacements de méthode virtuelle différents).Réponses:
Ok, il semble que je me sois trompé tout le temps, car pour tous mes exemples, il doit exister une table virtuelle pour l'objet de base, ce qui empêcherait au départ l'optimisation de base vide. Je vais laisser les exemples tenir car je pense qu'ils donnent des exemples intéressants de la raison pour laquelle les adresses uniques sont normalement une bonne chose à avoir.
Après avoir étudié tout cela plus en profondeur, il n'y a aucune raison technique de désactiver l'optimisation de classe de base vide lorsque le premier membre est du même type que la classe de base vide. C'est juste une propriété du modèle objet C ++ actuel.
Mais avec C ++ 20, il y aura un nouvel attribut
[[no_unique_address]]
qui indique au compilateur qu'un membre de données non statique peut ne pas avoir besoin d'une adresse unique (techniquement parlant, elle chevauche potentiellement [intro.object] / 7 ).Cela implique que (c'est moi qui souligne)
par conséquent, on peut "réactiver" l'optimisation de classe de base vide en donnant l'attribut au premier membre de données
[[no_unique_address]]
. J'ai ajouté un exemple ici qui montre comment cela (et tous les autres cas auxquels je pourrais penser) fonctionne.Mauvais exemples de problèmes à travers ce
Puisqu'il semble qu'une classe vide peut ne pas avoir de méthodes virtuelles, permettez-moi d'ajouter un troisième exemple:
Mais les deux derniers appels sont les mêmes.
Anciens exemples (ne répondent probablement pas à la question car les classes vides peuvent ne pas contenir de méthodes virtuelles, semble-t-il)
Considérez dans votre code ci-dessus (avec des destructeurs virtuels ajoutés) l'exemple suivant
Mais comment le compilateur doit-il distinguer ces deux cas?
Et peut-être un peu moins artificiel:
Mais les deux derniers sont les mêmes si nous avons une optimisation de classe de base vide!
la source
std::is_empty
sur cppreference est beaucoup plus élaboré. Même du projet en cours sur eel.is .dynamic_cast
quand ce n'est pas polymorphe (avec des exceptions mineures non pertinentes ici).