2010-01-06 8 views
9

Supposons que j'ai une classe commeEst-ce que l'appel du constructeur d'une classe vide utilise réellement de la mémoire?

class Empty{ 
    Empty(int a){ cout << a; } 
} 

Et puis je l'invoquons en utilisant

int main(){ 
    Empty(2); 
    return 0; 
} 

Est-ce que cette cause une mémoire à allouer sur la pile pour la création d'un objet « vide »? Évidemment, les arguments doivent être poussés sur la pile, mais je ne veux pas encourir de frais supplémentaires. Fondamentalement, j'utilise le constructeur en tant que membre statique.

La raison pour laquelle je veux faire cela est à cause de modèles. Le code actuel ressemble

template <int which> 
class FuncName{ 
    template <class T> 
    FuncName(const T &value){ 
     if(which == 1){ 
      // specific behavior 
     }else if(which == 2){ 
      // other specific behavior 
     } 
    } 
}; 

qui me permet d'écrire quelque chose comme

int main(){ 
    int a = 1; 
    FuncName<1>(a); 
} 

pour que je sois à se spécialiser un paramètre de modèle, sans avoir à spécifier le type de T. En outre, j'espère que le compilateur optimisera les autres branches à l'intérieur du constructeur. Si quelqu'un sait si c'est vrai ou comment vérifier, ce serait grandement apprécié. J'ai supposé aussi que jeter des modèles dans la situation ne change pas le problème de la "classe vide" d'en haut, n'est-ce pas?

+0

La question est de savoir pourquoi cela vous intéresse.C'est le travail du compilateur de prendre soin et de générer le meilleur code. Vous devriez vous concentrer sur l'écriture du code le plus expressif. –

+0

PS. Il n'y a aucune exigence pour que l'argument soit poussé sur la pile. L'ABI C++ n'est pas défini de façon délibérée, de sorte que les compilateurs ont la possibilité d'utiliser register pour transmettre des paramètres si cela rend le code plus efficace. –

+0

Je m'en soucie parce que je veux obtenir la meilleure performance possible; Je déteste vraiment l'attitude que le code devrait être élégant et ne pas s'inquiéter de ces choses. Parfois, ces choses comptent (je fais du calcul haute performance). –

Répondre

16

Stroustrup Citation:

Pourquoi la taille d'une classe vide non nul? Pour vous assurer que les adresses de deux objets différents seront différentes. Pour la même raison, "nouveau" renvoie toujours des pointeurs vers des objets distincts. Tenir compte:

class Empty { }; 

void f() 
{ 
    Empty a, b; 
    if (&a == &b) cout << "impossible: report error to compiler supplier"; 

    Empty* p1 = new Empty; 
    Empty* p2 = new Empty; 
    if (p1 == p2) cout << "impossible: report error to compiler supplier"; 
} 

Il y a une règle intéressante qui dit qu'une classe de base vide n'a pas besoin d'être représenté par un octet séparé:

struct X : Empty { 
    int a; 
    // ... 
}; 

void f(X* p) 
{ 
    void* p1 = p; 
    void* p2 = &p->a; 
    if (p1 == p2) cout << "nice: good optimizer"; 
} 

Cette optimisation est sûr et peut être le plus utile. Il permet à un programmeur d'utiliser des classes vides pour représenter des concepts très simples sans surcharge. Certains compilateurs actuels fournissent cette "optimisation de classe de base vide".

+0

Mais que se passe-t-il si l'objet créé n'est jamais référencé? Le compilateur est-il autorisé à ne pas réserver d'espace de pile ou à le nettoyer immédiatement même s'il n'est pas hors de portée? –

+0

Oui, je suis sûr qu'un compilateur éliminera complètement la mémoire réservée si elle n'est pas utilisée ou référencée. C'est une optimisation assez simple. Selon le compilateur, bien sûr. –

+1

Tant que le behvior n'est pas modifié, le compilateur est libre d'éliminer tout le concept de l'objet sous le capot et le tout peut être incorporé dans le code et toutes les valeurs stockées dans les registres. –

2

Cela pourrait, peut-être, pas, selon les circonstances. Si vous dites:

Empty e; 
Empty * ep = & e; 

alors évidemment les choses doivent être allouées.

+0

Le compilateur doit-il nécessairement émettre du code _allocate_ pour lui donner une adresse? Y at-il une raison pour laquelle & e ne peut pas être une adresse qui n'est ni tas ni pile, et où il n'y a pas d'objet réel? – James

+0

@Autopulated: Je crois, voir le commentaire de Richard Pennington. La question la plus intéressante est si vous ne demandez jamais son adresse; alors faut-il encore en avoir un? (Une question très zen) –

+1

Si vous prenez l'adresse de e, puis faites quelque chose avec cette adresse (ce que mon code d'exemple ne fait pas), alors e doit être instancié - c'est-à-dire alloué. –

2

Essayez-le et voir. De nombreux compilateurs élimineront ces objets temporaires lorsqu'on leur demandera d'optimiser leur sortie.

Si le démontage est trop complexe, puis créer deux fonctions avec un nombre différent de ces objets et de voir s'il y a une différence dans les emplacements de pile d'objets qui les entourent, quelque chose comme:

void empty1 (int x) 
{ 
    using namespace std; 

    int a; 
    Empty e1 (x); 
    int b; 

    cout << endl; 
    cout << "empty1" << endl; 
    cout << hex << int (&x) << " " << dec << (&x - &a) << endl; 
    cout << hex << int (&a) << " " << dec << (&a - &b) << endl; 
} 

puis essayer en cours d'exécution que par rapport à une fonction empty8 avec huit vides créés. Avec g ++ sur x86, si vous prenez l'adresse de l'un des vides, vous obtenez un emplacement entre x et a sur la pile, incluant donc x dans la sortie. Vous ne pouvez pas supposer que le stockage des objets se terminera dans le même ordre que celui déclaré dans le code source.

+0

Comment? Je regarde la liste d'assemblage g ++ pour un exemple simple et c'est très difficile à suivre. J'utilise 'g ++ -c -g -O2 -Wa, -ahl = fichier.sport.cpp' –

+0

Vous pourriez obtenir une meilleure compilation vers un exécutable (avec des informations de débogage), puis utiliser' objdump -dS', et Recherchez les lignes source qui vous intéressent. Puisque vous avez activé l'optimisation, rappelez-vous que la même ligne source est susceptible d'apparaître à plusieurs endroits dans le désassemblage. –