Quand les programmeurs utilisent-ils l’optimisation de base vide (EBO)?

Je lisais à propos de l’optimisation de base vide (EBO). Pendant la lecture, les questions suivantes se sont posées dans mon esprit:

  1. Quel est l’intérêt d’utiliser la classe vide comme classe de base quand elle ne consortingbue en rien aux classes dérivées (ni en termes de fonctionnalités, ni en termes de données) ?

  2. Dans cet article , je lis ceci:

// S est vide
classe struct T: S
{
int x;
};

[…]

Notez que nous n’avons perdu aucune précision des données ou du code: lorsque vous créez un object autonome de type S, la taille de l’object est toujours égale à 1 (ou plus) comme auparavant; Ce n’est que lorsque S est utilisé comme classe de base d’une autre classe que son encombrement mémoire diminue à zéro. Pour réaliser l’impact de cette économie, imaginez un vecteur contenant 125 000 objects. L’EBO à lui seul permet d’économiser un demi-mégaoctet de mémoire!

Cela signifie-t-il que si nous n’utilisons pas “S” comme classe de base de “T”, nous consumrions nécessairement deux fois plus de mégaoctets de mémoire? Je pense que l’article compare deux scénarios différents que je ne pense pas être correct.

J’aimerais connaître un scénario réel dans lequel l’utilisation efficace des OBE peut s’avérer utile (cela signifie que, dans le même scénario, nous serions nécessairement perdus SI nous n’utilisions pas les OBE!).

Veuillez noter que si votre réponse contient des explications comme celle-ci:

Le problème est qu’une classe vide a une taille non nulle, mais lorsque dérivée ou dérivée, elle peut avoir une taille nulle, je ne vous le demande pas, car je le sais déjà. Ma question est la suivante: pourquoi quelqu’un tirerait-il sa classe d’une classe vide en premier lieu? Même s’il ne dérive pas et écrit simplement sa classe (sans base vide), est-il perdu de n’importe quelle manière?

EBO est important dans le contexte de la conception basée sur des stratégies , où vous héritez généralement en privé de plusieurs classes de stratégies. Si nous prenons l’exemple d’une politique de sécurité des threads, on pourrait imaginer le pseudo-code:

class MTSafePolicy { public: void lock() { mutex_.lock(); } void unlock() { mutex_.unlock(); } private: Mutex mutex_; }; class MTUnsafePolicy { public: void lock() { /* no-op */ } void unlock() { /* no-op */ } }; 

Étant donné une classe de conception basée sur une politique, telle que:

 template class Test : ThreadSafetyPolicy { /* ... */ }; 

L’utilisation de la classe avec un MTUnsafePolicy n’ajoute aucune surcharge de taille à la classe Test : c’est un exemple parfait de ne pas payer pour ce que vous n’utilisez pas .

EBO n’est pas vraiment une optimisation (du moins pas celle que vous faites dans le code). Le point essentiel est qu’une classe vide a une taille non nulle, mais lorsqu’elle est dérivée ou dérivée, elle peut avoir une taille nulle.

C’est le résultat le plus habituel:

 class A { }; class B { }; class C { }; class D : C { }; #include  using namespace std; int main() { cout << "sizeof(A) + sizeof(B) == " << sizeof(A)+sizeof(B) << endl; cout << "sizeof(D) == " << sizeof(D) << endl; return 0; } 

Sortie:

 sizeof(A) + sizeof(B) == 2 sizeof(D) == 1 

Pour le modifier: L’optimisation est que si vous dérivez réellement (par exemple d’un foncteur ou d’une classe qui n’a que des membres statiques), la taille de votre classe (qui dérive) n’augmentera pas de 1 (ou plus probablement 4 ou 8 en raison d'octets de remplissage).

“Optimisation” dans EBO signifie que lorsque vous utilisez la classe de base, vous pouvez optimiser l’utilisation de la mémoire par rapport à un membre du même type. Ie vous comparez

 struct T : S { int x; }; 

avec

 struct T { S s; int x; }; 

pas avec

 struct T { int x; }; 

Si votre question est de savoir pourquoi vous auriez une classe vide (en tant que membre ou en tant que base), c’est parce que vous utilisez ses fonctions de membre. Vide signifie qu’il n’y a pas de membre de données, pas qu’il n’en ait aucun. Des opérations de ce type sont souvent effectuées lors de la programmation avec des modèles, où la classe de base est parfois “vide” (pas de données membres) et parfois non.

Il est utilisé lorsque les programmeurs souhaitent exposer certaines données au client sans augmenter la taille de la classe du client. La classe vide peut contenir des énumérations et des typedefs ou des définitions que le client peut utiliser. La manière la plus judicieuse d’utiliser une telle classe, hérite d’une telle classe en privé. Cela cachera les données de l’extérieur et n’augmentera pas la taille de votre classe.

EASTL a une bonne explication de la raison pour laquelle ils ont besoin d’EBO, elle est également expliquée en profondeur dans le document auquel ils se rattachent / crédit

EBO n’est pas quelque chose que le programmeur influence, et / ou le programmeur serait puni s’il (s) choisissait de ne pas dériver d’une classe de base vide.

Le compilateur contrôle si pour:

 class X : emptyBase { int X; }; class Y { int x }; 

vous obtenez sizeof(X) == sizeof(Y) ou non. Si vous le faites, le compilateur implémente EBO, sinon ce n’est pas le cas.

Il n’y a jamais de situation où la sizeof(Y) > sizeof(X) se produirait.

La plupart du temps, une classe de base vide est utilisée de manière polymorphe (que l’article mentionne), en tant que classe “tag” ou en tant que classe d’exception (bien que celles-ci soient généralement dérivées de std :: exception, qui n’est pas vide). Parfois, il existe une bonne raison de développer une hiérarchie de classe qui commence par une classe de base vide.

Boost.CompressedPair utilise EBO pour réduire la taille des objects dans le cas où l’un des éléments est vide.

Le principal avantage auquel je peux penser est dynamic_cast. Vous pouvez prendre un pointeur sur S et tenter de le dynamiser_cast sur tout ce qui hérite de S, en supposant que S offre une fonction virtuelle semblable à un destructeur virtuel, ce qui doit être fait en tant que classe de base. Si vous implémentiez, par exemple, un langage à typage dynamic, vous souhaiterez peut-être ou obliger chaque type à dériver d’une classe de base uniquement à des fins de stockage effacé et de vérification de type via dynamic_cast.