Pourquoi ne puis-je pas hériter de l’int en C ++?

J’adorerais pouvoir faire ça:

class myInt : public int { }; 

Pourquoi je ne peux pas?

Pourquoi voudrais-je? Taper plus fort. Par exemple, je pourrais définir deux classes intA et intB , ce qui me permet de faire intA + intA ou intB + intB , mais pas intA + intB .

“Ints ne sont pas des classes.” Et alors?

“Ints n’a aucune donnée de membre.” Oui, ils ont 32 bits, ou peu importe.

“Ints n’a aucune fonction membre.” Eh bien, ils ont tout un tas d’opérateurs comme + et - .

    Le commentaire de Neil est assez précis. Bjarne a mentionné avoir considéré et rejeté cette possibilité exacte 1 :

    La syntaxe de l’initialiseur était illégale pour les types intégrés. Pour permettre cela, j’ai introduit la notion que les types intégrés ont des constructeurs et des destructeurs. Par exemple:

     int a(1); // pre-2.1 error, now initializes a to 1 

    J’ai envisagé d’étendre cette notion pour permettre la dérivation des classes intégrées et la déclaration explicite des opérateurs intégrés pour les types intégrés. Cependant, je me suis retenu.

    Permettre la dérivation d’un int ne donne pas vraiment de nouveauté à un programmeur C ++ par rapport à un membre int . Cela est principalement dû au fait que int ne possède aucune fonction virtuelle à remplacer par la classe dérivée. Plus sérieusement, les règles de conversion C sont tellement chaotiques que prétendre que int , short , etc., sont des comportements normaux, ne fonctionnera pas. Ils sont soit compatibles C, soit ils obéissent aux règles C ++ relativement bien comscopes pour les classes, mais pas les deux.

    En ce qui concerne le commentaire que la performance justifie de ne pas créer une classe, c’est (au moins principalement) faux. Dans Smalltalk, tous les types sont des classes – mais presque toutes les implémentations de Smalltalk ont ​​des optimisations, de sorte que l’implémentation peut être essentiellement identique à la manière de réaliser un travail de type non-classe. Par exemple, la classe smallInteger représente un entier de 15 bits, et le message ‘+’ est codé en dur dans la machine virtuelle. Même si vous pouvez dériver de smallInteger, cela donne toujours des performances similaires à celles d’un type intégré ( Bien que Smalltalk soit assez différent de C ++, les comparaisons de performances directes sont difficiles et peu susceptibles de signifier beaucoup).

    Edit: le seul bit “gaspillé” dans l’implémentation Smalltalk de smallInteger ne serait probablement pas nécessaire en C ou C ++. Smalltalk est un peu comme Java – lorsque vous “définissez un object”, vous définissez simplement un pointeur sur un object, et vous devez lui affecter dynamicment un object. Ce que vous manipulez, passez à une fonction en tant que paramètre, etc., est toujours le pointeur, pas l’object lui-même.

    Ce n’est pas comme cela que smallInteger est implémenté – dans son cas, ils placent la valeur entière directement dans ce qui serait normalement le pointeur. Pour faire la distinction entre un smallInteger et un pointeur, ils obligent tous les objects à être alloués à des frontières d’octets pairs, de sorte que le LSB est toujours clair. Un smallInteger a toujours le jeu LSB.

    La plupart de ces informations sont nécessaires, car Smalltalk est typé dynamicment – il doit pouvoir en déduire le type en examinant la valeur elle-même, et smallInteger utilise essentiellement ce LSB en tant que balise de type. Étant donné que C ++ est typé statiquement, il n’est jamais nécessaire de déduire le type de la valeur, vous n’auriez donc probablement pas besoin de gaspiller ce bit.

    1 Dans la conception et l’évolution de C ++ , §15.11.3.

    Int est un type ordinal, pas une classe. Pourquoi voudriez-vous?

    Si vous devez append des fonctionnalités à “int”, envisagez de créer une classe d’agrégat qui comporte un champ entier et des méthodes exposant les fonctionnalités supplémentaires dont vous avez besoin.

    Mettre à jour

    @OP “Ints ne sont pas des classes” alors?

    L’inheritance , le polymorphism et l’encapsulation sont des clés de voûte de la conception orientée object . Aucune de ces choses ne s’applique aux types ordinaux. Vous ne pouvez pas hériter d’un int car ce n’est qu’un paquet d’octets et n’a pas de code.

    Ints, chars et autres types ordinaux n’ont pas de tables de méthodes , il n’y a donc aucun moyen d’append des méthodes ou de les remplacer, ce qui est vraiment le cœur de l’inheritance.

    Pourquoi voudrais-je? Taper plus fort. Par exemple, je pourrais définir deux classes intA et intB, ce qui me permet de faire intA + intA ou intB + intB, mais pas intA + intB.

    Ça n’a aucun sens. Vous pouvez faire tout cela sans hériter de quoi que ce soit. (D’autre part, je ne vois pas comment vous pourriez éventuellement y parvenir en utilisant l’inheritance.) Par exemple,

     class SpecialInt { ... }; SpecialInt operator+ (const SpecialInt& lhs, const SpecialInt& rhs) { ... } 

    Remplissez les blancs et vous avez un type qui résout votre problème. Vous pouvez faire SpecialInt + SpecialInt ou int + int , mais SpecialInt + int ne comstackra pas exactement comme vous le souhaitiez.

    Par contre, si nous prétendions que l’inheritance de int était légal et que notre SpecialInt dérivé de int , alors SpecialInt + int serait compilé. L’inheritance causerait le problème exact que vous voulez éviter. Ne pas hériter évite facilement le problème.

    “Ints n’a aucune fonction membre.” Eh bien, ils ont tout un tas d’opérateurs comme + et -.

    Ce ne sont pas des fonctions membres cependant.

    Si l’OP veut vraiment comprendre POURQUOI C ++ est comme ça, alors il devrait avoir une copie du livre de Stroustup “The Design and Evolution of C ++”. Cela explique les raisons de cette décision et de nombreuses autres décisions de conception dans les premiers jours du C ++.

    Parce que int est un type natif et non une classe

    Edit: déplacer mes commentaires dans ma réponse.

    Cela vient du pasortingmoine C et de ce que représentent exactement les primitives. Une primitive dans c ++ n’est qu’une collection d’octets qui ont peu de sens, sauf pour le compilateur. Une classe, par contre, possède une table de fonctions et, une fois que vous avez commencé à parcourir le chemin d’inheritance et d’inheritance virtuel, vous avez une vtable. Rien de tout cela n’est présent dans une primitive, et en le rendant présent, vous seriez en mesure de: casser beaucoup de code c supposant qu’un int ne contient que 8 octets et b) faire en sorte que les programmes prennent beaucoup plus de mémoire.

    Pensez-y autrement. int / float / char n’a aucun membre ou méthode de données. Pensez aux primitifs comme aux quarks – ce sont les blocs de construction que vous ne pouvez pas subdiviser, vous les utilisez pour faire de plus grandes choses (excuses si mon analogie est un peu décevante, je ne connais pas assez de physique des particules)

    fort typage de ints (et floats etc) en c ++

    Scott Meyer ( Effective c ++ offre une solution très efficace à votre problème de typage fort des types de base en c ++, et cela fonctionne comme ceci:

    Le typage fort est un problème qui peut être traité et évalué au moment de la compilation , ce qui signifie que vous pouvez utiliser les ordinaux (typage faible) pour plusieurs types au moment de l’exécution dans les applications déployées et utiliser une phase de compilation spéciale pour éliminer les combinaisons de types inappropriées au moment de la compilation.

     #ifdef STRONG_TYPE_COMPILE typedef time Time typedef distance Distance typedef velocity Velocity #else typedef time float typedef distance float typedef velocity float #endif 

    Vous définissez ensuite votre Time , votre Mass et votre Distance comme étant des classes avec tous (et seulement) les opérateurs appropriés surchargés aux opérations appropriées. En pseudo-code:

     class Time { public: float value; Time operator +(Time b) {self.value + b.value;} Time operator -(Time b) {self.value - b.value;} // don't define Time*Time, Time/Time etc. Time operator *(float b) {self.value * b;} Time operator /(float b) {self.value / b;} } class Distance { public: float value; Distance operator +(Distance b) {self.value + b.value;} // also -, but not * or / Velocity operator /(Time b) {Velocity( self.value / b.value )} } class Velocity { public: float value; // appropriate operators Velocity(float a) : value(a) {} } 

    Une fois cela fait, votre compilateur vous indiquera tous les endroits où vous avez enfreint les règles encodées dans les classes ci-dessus.

    Je vais vous laisser travailler le rest des détails ou acheter le livre.

    Ce que les autres ont dit est vrai … int est une primitive en C ++ (un peu comme C #). Cependant, vous pouvez réaliser ce que vous vouliez en construisant une classe autour de l’ int :

     class MyInt { private: int mInt; public: explicit MyInt(int in) { mInt = in; } // Getters/setters etc }; 

    Vous pouvez alors hériter de tout ce que vous voulez.

    Personne n’a mentionné que C ++ avait été conçu pour avoir (principalement) une compatibilité avec C, de manière à faciliter le chemin de mise à niveau pour les codeurs C, d’où le défaut par défaut pour tous les membres du public, etc.

    Avoir int comme une classe de base que vous pourriez remplacer compliquerait fondamentalement cette règle sans fin et rendrait la mise en œuvre du compilateur infernale qui, si vous voulez que les fournisseurs de codeurs et de compilateurs existants prennent en charge votre langage naissant

    En C ++, les types intégrés ne sont pas des classes.

    Comme d’autres, je ne peux pas le faire car int est un type primitif.

    Je comprends la motivation, cependant, si c’est pour une frappe plus forte. Il a même été proposé pour C ++ 0x qu’un type spécial de typedef soit suffisant pour cela (mais cela a été rejeté?).

    Peut-être que quelque chose pourrait être réalisé si vous fournissiez vous-même l’emballage de base. Par exemple, quelque chose comme ce qui suit, qui, espérons-le, utilise des modèles curieusement récurrents d’une manière légale, et nécessite seulement de dériver une classe et de fournir un constructeur approprié:

     template  class Wrapper { T n; public: Wrapper(T n = T()): n(n) {} T& value() { return n; } T value() const { return n; } Child operator+= (Wrapper other) { return Child(n += other.n); } //... many other operators }; template  Child operator+(Wrapper lhv, Wrapper rhv) { return Wrapper(lhv) += rhv; } //Make two different kinds of "int"'s struct IntA : public Wrapper { IntA(int n = 0): Wrapper(n) {} }; struct IntB : public Wrapper { IntB(int n = 0): Wrapper(n) {} }; #include  int main() { IntA a1 = 1, a2 = 2, a3; IntB b1 = 1, b2 = 2, b3; a3 = a1 + a2; b3 = b1 + b2; //a1 + b1; //bingo //a1 = b1; //bingo a1 += a2; std::cout << a1.value() << ' ' << b3.value() << '\n'; } 

    Mais si vous conseillez de définir un nouveau type et de surcharger les opérateurs, vous pouvez consulter Boost.Operators

    Eh bien, vous n’avez pas besoin d’hériter de tout ce qui n’a pas de fonctions membres virtuelles. Donc, même si int était une classe, la composition ne serait pas supérieure.

    En d’autres termes, l’inheritance virtuel est la seule véritable raison pour laquelle vous auriez besoin d’un inheritance. Tout le rest ne fait que vous faire économiser beaucoup de temps de frappe. Et je ne pense pas qu’un type / type int avec des membres virtuels soit la chose la plus intelligente à imaginer dans le monde C ++. Au moins pas pour vous tous les jours int .

    Qu’est-ce que cela signifie d’hériter d’un int?

    “int” n’a aucune fonction membre; il n’a pas de données de membre, c’est une représentation de 32 (ou 64) bits en mémoire. Il n’a pas sa propre vtable. Tout ce qu’il “possède” (il ne les possède même pas vraiment) sont des opérateurs comme + – / * qui sont vraiment des fonctions plus globales que les fonctions membres.

    Vous pouvez obtenir ce que vous voulez avec des typedefs forts. Voir BOOST_STRONG_TYPEDEF

    Plus général que le fait que “int est primitif” est ceci: int est un type scalaire , alors que les classes sont des types d’ agrégats . Un scalaire est une valeur atomique, tandis qu’un agrégat est quelque chose avec des membres. L’inheritance (au moins tel qu’il existe en C ++) n’a de sens que pour un type d’agrégat, car vous ne pouvez pas append de membres ou de méthodes aux scalaires – par définition, ils n’ont aucun membre.

    Cette réponse est une implémentation de la réponse d’UncleBens

    mettre dans Primitive.hpp

     #pragma once template class Primitive { protected: T value; public: // we must type cast to child to so // a += 3 += 5 ... and etc.. work the same way // as on primitives Child &childRef(){ return *((Child*)this); } // you can overload to give a default value if you want Primitive(){} explicit Primitive(T v):value(v){} T get(){ return value; } #define OP(op) Child &operator op(Child const &v){\ value op v.value; \ return childRef(); \ } // all with equals OP(+=) OP(-=) OP(*=) OP(/=) OP(<<=) OP(>>=) OP(|=) OP(^=) OP(&=) OP(%=) #undef OP #define OP(p) Child operator p(Child const &v){\ Child other = childRef();\ other p ## = v;\ return other;\ } OP(+) OP(-) OP(*) OP(/) OP(<<) OP(>>) OP(|) OP(^) OP(&) OP(%) #undef OP #define OP(p) bool operator p(Child const &v){\ return value p v.value;\ } OP(&&) OP(||) OP(<) OP(<=) OP(>) OP(>=) OP(==) OP(!=) #undef OP Child operator +(){return Child(value);} Child operator -(){return Child(-value);} Child &operator ++(){++value; return childRef();} Child operator ++(int){ Child ret(value); ++value; return childRef(); } Child operator --(int){ Child ret(value); --value; return childRef(); } bool operator!(){return !value;} Child operator~(){return Child(~value);} }; 

    Exemple:

     #include "Primitive.hpp" #include  using namespace std; class Integer : public Primitive { public: Integer(){} Integer(int a):Primitive(a) {} }; int main(){ Integer a(3); Integer b(8); a += b; cout << a.get() << "\n"; Integer c; c = a + b; cout << c.get() << "\n"; cout << (a > b) << "\n"; cout << (!b) << " " << (!!b) << "\n"; } 

    S’il vous plaît, excusez-moi pour mon pauvre anglais.

    Il y a une différence majeure entre la construction C ++ correcte comme ceci:

     struct Length { double l; operator =!?:%+-*/...(); }; struct Mass { double l; operator =!?:%+-*/...(); }; 

    et l’extension proposée

     struct Length : public double ; struct Mass : public double ; 

    Et cette différence réside dans le mot this clé this comportement. this s’agit d’un pointeur et l’utilisation d’un pointeur laisse peu de chances d’utiliser des registres pour les calculs, car les registres de processeurs habituels n’ont pas d’adresse. Pire encore, l’utilisation du pointeur rend le compilateur suspect sur le fait que deux pointeurs peuvent désigner la même mémoire.

    Cela va imposer un lourd fardeau au compilateur pour optimiser les opérations sortingviales.

    Un autre problème concerne le nombre de bogues: reproduire exactement tout le comportement des opérateurs est absolument sujet aux erreurs (pour que le constructeur explicite n’interdit pas tous les cas implicites). La probabilité d’erreur lors de la construction d’un tel object est assez élevée. Il n’est pas équivalent d’avoir la possibilité de faire quelque chose en travaillant dur ou de le faire déjà.

    Les implémenteurs du compilateur introduiraient du code de vérification de type (avec peut-être quelques erreurs, mais l’exactitude du compilateur est bien meilleure que le code client, car un bogue dans le compilateur génère d’innombrables erreurs), mais le comportement principal rest le même que d’habitude.

    La solution de remplacement proposée (utiliser des structures pendant la phase de débogage et des flottants réels lorsque ceux-ci sont optimisés) est intéressante mais présente des inconvénients: elle augmente la probabilité d’avoir des bogues uniquement dans les versions optimisées. Et le débogage d’applications optimisées est très coûteux.

    On peut implémenter une bonne proposition pour la demande initiale de @Rocketmagnet pour les types d’entiers utilisant:

     enum class MyIntA : long {}; auto operator=!?:%+-*/...(MyIntA); MyIntA operator "" _A(long); 

    Le niveau de bogue sera assez élevé, comme l’utilisation d’un tour simple, mais le compilateur traitera ces types exactement comme des entiers intégrés (y compris la capacité et l’optimisation des registres), merci pour l’inlining.

    Mais cette astuce ne peut pas être utilisée (malheureusement) pour les nombres flottants, et le plus beau besoin est évidemment de vérifier les dimensions réelles. On ne peut pas mélanger les pommes et les poires: append de la longueur et de la surface est une erreur courante.

    L’invocation de Stroustrup par @Jerry n’est pas pertinente. La virtualité a un sens principalement pour l’inheritance public, et le besoin est là pour l’inheritance privé. Les règles de conversion C chaotiques (le C ++ 14 n’a-t-il rien de chaotique?) De type basique ne sont pas non plus utiles: l’objective n’est pas d’avoir des règles de conversion par défaut, mais de ne pas suivre les règles standard.

    Si je me souviens bien, c’était la principale – ou l’une des – principales raisons pour lesquelles C ++ n’était pas considéré comme un véritable langage orienté object. Les gens de Java diraient: “En Java, TOUT est un object”;)

    Ceci est lié à la manière dont les éléments sont stockés en mémoire. Un int en C ++ est un type intégral, comme mentionné ailleurs, et ne contient que 32 ou 64 bits (un mot) en mémoire. Un object, cependant, est stocké différemment dans la mémoire. Il est généralement stocké sur le tas et possède des fonctionnalités liées au polymorphism.

    Je ne sais pas comment l’expliquer mieux. Comment hériterais-tu du numéro 4?

    Why can’t you inherit from int, even though you might want to?

    Performance

    There’s no functional reason why you shouldn’t be able (in an arbitrary language) inherit from ordinal types such as int, or char, or char* etc. Some languages such as Java and Objective-C actually provide class/object (boxed) versions of the base type, in order to satisfy this need (as well as to deal with some other unpleasant consequences of ordinal types not being objects):

     language ordinal type boxed type, c++ int ? java int Integer objective-c int NSNumber 

    But even Java and objective-c preserve their ordinal types for use… why?

    The simple reasons are performance and memory consumption. An ordinal type can be typically be constructed, operated upon, and passed by value in just one or two X86 instructions, and only consumes a few bytes at worst. A class typically cannot – it often uses 2x or more as much memory, and manipulating its value may take many hundreds of cycles.

    This means programmers who understand this will typically use the ordinal types to implement performance or memory usage sensitive code, and will demand that language developers support the base types.

    It should be noted that quite a few languages do not have ordinal types, in particular the dynamic languages such as perl , which relies almost entirely on a variadic type, which is something else altogether, and shares some of the overhead of classes.