sintaxis resueltos lenguaje herencia funciones estructuras ejercicios ejemplos codigo clases c++ inheritance integer language-design strong-typing

resueltos - lenguaje c++ ejemplos



¿Por qué no puedo heredar de int en C++? (19)

¿Por qué querría? Mecanografía más fuerte. Por ejemplo, podría definir dos clases intA e intB, que me permiten hacer intA + intA o intB + intB, pero no intA + intB.

Eso no tiene sentido. Puedes hacer todo eso sin heredar de nada. (Y, por otro lado, no veo cómo podría lograrlo utilizando la herencia.) Por ejemplo,

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

Complete los espacios en blanco, y tiene un tipo que resuelve su problema. Puede hacer SpecialInt + SpecialInt o int + int , pero SpecialInt + int no se compilará exactamente como lo desea.

Por otro lado, si fingíamos que heredar de int era legal, y nuestro SpecialInt derivado de int , entonces SpecialInt + int compilaría. Heredar causaría el problema exacto que desea evitar. No heredar evita el problema fácilmente.

"Los Ints no tienen ninguna función de miembro". Bueno, tienen un montón de operadores como + y -.

Sin embargo, esas no son funciones de miembros.

Me encantaría poder hacer esto:

class myInt : public int { };

¿Por qué no puedo?

¿Por qué querría? Mecanografía más fuerte. Por ejemplo, podría definir dos clases intA e intB , que me permiten hacer intA + intA o intB + intB , pero no intA + intB .

"Ints no son clases". ¿Y qué?

"Ints no tiene ningún dato de miembro". Sí lo hacen, tienen 32 bits, o lo que sea.

"Los Ints no tienen ninguna función de miembro". Bueno, tienen un montón de operadores como + y - .


tipificación fuerte de ints (y flotantes, etc.) en c ++

Scott Meyer ( Effective c ++ tiene una solución muy efectiva y poderosa para su problema de hacer un tipado fuerte de tipos base en c ++, y funciona así:

La tipificación fuerte es un problema que puede abordarse y evaluarse en tiempo de compilación , lo que significa que puede usar los ordinales (tipificación débil) para múltiples tipos en tiempo de ejecución en aplicaciones implementadas y usar una fase de compilación especial para eliminar combinaciones inadecuadas de tipos en tiempo de compilación

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

A continuación, define su Time , Mass , Distance para que sean clases con todos (y solo) los operadores apropiados sobrecargados en las operaciones apropiadas. En pseudo-código:

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) {} }

Una vez hecho esto, su compilador le dirá en qué lugares ha violado las reglas codificadas en las clases anteriores.

Te dejaré resolver el resto de los detalles tú mismo o comprar el libro.


¿Qué significa heredar de un int?

"int" no tiene funciones de miembro; no tiene datos de miembro, es una representación de 32 (o 64) bits en la memoria. No tiene su propio vtable. Todo lo que "tiene" (en realidad ni siquiera los posee) son algunos operadores como + - / * que en realidad son funciones más globales que las funciones de miembro.


Bueno, realmente no necesitas heredar nada que no tenga ninguna función de miembro virtual. Así que incluso si int fuera una clase, no habría una ventaja sobre la composición.

Por así decirlo, la herencia virtual es la única razón real por la que necesitaría herencia de todos modos; todo lo demás te permite ahorrar una gran cantidad de tiempo de tipeo. Y no creo que una clase / tipo int con miembros virtuales sea lo más inteligente que se pueda imaginar en el mundo de C ++. Al menos no para ti todos los días int .


Como otros que digo, no se puede hacer ya que int es un tipo primitivo.

Entiendo la motivación, sin embargo, si es para escribir con mayor fuerza. Incluso se ha propuesto para C ++ 0x que un tipo especial de typedef debería ser suficiente para eso (¿pero esto ha sido rechazado?).

Quizás se podría lograr algo, si proporcionó la envoltura de base usted mismo. Por ejemplo, algo como el siguiente, que con suerte utiliza plantillas curiosamente recurrentes de una manera legal, y requiere solo derivar una clase y proporcionar un constructor adecuado:

template <class Child, class T> 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 <class Child, class T> Child operator+(Wrapper<Child, T> lhv, Wrapper<Child, T> rhv) { return Wrapper<Child, T>(lhv) += rhv; } //Make two different kinds of "int"''s struct IntA : public Wrapper<IntA, int> { IntA(int n = 0): Wrapper<IntA, int>(n) {} }; struct IntB : public Wrapper<IntB, int> { IntB(int n = 0): Wrapper<IntB, int>(n) {} }; #include <iostream> 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''; }

Pero si toma el consejo de que debe definir un nuevo tipo y sobrecargar a los operadores, puede echar un vistazo a Boost.Operators


El comentario de Neil es bastante preciso. Bjarne mencionó considerar y rechazar esta posibilidad exacta 1 :

La sintaxis del inicializador solía ser ilegal para tipos incorporados. Para permitirlo, introduje la noción de que los tipos incorporados tienen constructores y destructores. Por ejemplo:

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

Consideré extender esta noción para permitir la derivación de las clases incorporadas y la declaración explícita de los operadores incorporados para los tipos incorporados. Sin embargo, me contuve.

Permitir la derivación desde un int realmente no le da a un programador de C ++ nada significativamente nuevo en comparación con tener un miembro int . Esto se debe principalmente a que int no tiene ninguna función virtual para que la clase derivada se anule. Más en serio, las reglas de conversión de C son tan caóticas que pretender que las clases ordinarias int , short , etc. se comportan bien no va a funcionar. Son compatibles con C o obedecen las reglas de C ++ relativamente buenas para las clases, pero no ambas.

En cuanto al comentario, el rendimiento justifica no hacer int en una clase, es (al menos en su mayoría) falso. En Smalltalk, todos los tipos son clases, pero casi todas las implementaciones de Smalltalk tienen optimizaciones, por lo que la implementación puede ser esencialmente idéntica a cómo haría funcionar un tipo no de clase. Por ejemplo, la clase smallInteger representa un entero de 15 bits, y el mensaje ''+'' está codificado en la máquina virtual, por lo tanto, aunque se puede derivar de smallEntero, aún ofrece un rendimiento similar al tipo incorporado ( aunque Smalltalk es lo suficientemente diferente de C ++ que las comparaciones de rendimiento directo son difíciles y es poco probable que signifiquen mucho).

Editar: el único bit que está "desperdiciado" en la implementación de Smalltalk de SmallInteger probablemente no sería necesario en C o C ++. Smalltalk es un poco como Java: cuando "defines un objeto" realmente solo defines un puntero a un objeto, y tienes que asignar dinámicamente un objeto para que apunte. Lo que manipulas, pasas a una función como parámetro, etc., siempre es solo el puntero, no el objeto mismo.

No obstante, no es así como se implementa smallInteger; en su caso, ponen el valor entero directamente en lo que normalmente sería el puntero. Para distinguir entre un pequeño entero y un puntero, obligan a asignar todos los objetos en los límites de un byte par, por lo que el LSB siempre es claro. Un pequeño integrante siempre tiene el conjunto LSB.

Sin embargo, la mayoría de esto es necesario porque Smalltalk se tipea dinámicamente; tiene que ser capaz de deducir el tipo observando el valor en sí mismo, y smallInteger básicamente está utilizando ese LSB como una etiqueta de tipo. Dado que C ++ está tipado estáticamente, nunca es necesario deducir el tipo del valor, por lo que probablemente no necesite desperdiciar ese bit.

1 En El diseño y la evolución de C ++ , §15.11.3.


En C ++, los tipos incorporados no son clases.


Esta respuesta es una implementación de la respuesta de UncleBens

poner en Primitive.hpp

#pragma once template<typename T, typename Child> 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);} };

Ejemplo:

#include "Primitive.hpp" #include <iostream> using namespace std; class Integer : public Primitive<int, Integer> { public: Integer(){} Integer(int a):Primitive<int, Integer>(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"; }


Int es un tipo ordinal, no una clase. ¿Por qué querrías?

Si necesita agregar funcionalidad a "int", considere construir una clase agregada que tenga un campo entero, y métodos que expongan las capacidades adicionales que requiera.

Actualizar

@OP "Los Ints no son clases" ¿entonces?

La herencia , el polimorfismo y la encapsulación son piedras angulares del diseño orientado a objetos . Ninguna de estas cosas se aplica a los tipos ordinales. No puede heredar de un int porque es solo un grupo de bytes y no tiene código.

Ints, chars y otros tipos ordinales no tienen tablas de métodos , por lo que no hay forma de agregar métodos o anularlos, que es realmente el corazón de la herencia.


Lo que otros han dicho es verdad ... int es una primitiva en C ++ (muy similar a C #). Sin embargo, puedes lograr lo que deseas con solo construir una clase en int :

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

Entonces puedes heredar de eso todo lo que deseas.


Más general que el hecho de que "int es primitivo" es esto: int es un tipo escalar , mientras que las clases son tipos agregados . Un escalar es un valor atómico, mientras que un agregado es algo con miembros. La herencia (al menos tal como existe en C ++) solo tiene sentido para un tipo agregado, porque no puede agregar miembros o métodos a escalares; por definición, no tienen ningún miembro.


Nadie mencionó que C ++ fue diseñado para tener (principalmente) compatibilidad con versiones anteriores de C, a fin de facilitar la ruta de actualización para los codificadores C y, por lo tanto, struct predeterminados para todos los miembros, etc.

Tener int como una clase base que podría anular complicaría fundamentalmente esa regla sin fin y haría que la implementación del compilador sea infernal, y si usted quiere que los programadores y compiladores existentes respalden su incipiente lenguaje probablemente no valga la pena.


Por favor, discúlpeme por mi pobre inglés.

Hay una gran diferencia entre la construcción correcta de C ++ como esta:

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

y la extensión propuesta

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

Y esta diferencia radica en la palabra clave this comportamiento. this es un puntero y usar un puntero deja pocas oportunidades de usar registros para los cálculos, porque en los registros de procesadores usuales no tiene dirección. Lo peor, usar el puntero hace que el compilador sospeche que dos punteros pueden designar la misma memoria.

Esto pondrá una carga extraordinaria en el compilador para optimizar operaciones triviales.

Otro problema es el número de errores: reproducir exactamente todo el comportamiento de los operadores es absolutamente propenso a errores (por ejemplo, hacer que el constructor sea explícito no prohíbe todos los casos de implicitos). La probabilidad de error al construir tal objeto es bastante alta. No es equivalente tener la posibilidad de hacer algo a través del trabajo duro o de haberlo hecho ya.

Los implementadores de compiladores introducirían código de comprobación de tipo (con algunos errores quizás, pero la exactitud del compilador es mucho mejor que el código del cliente, debido a cualquier error en el compilador generando innumerables errores), pero el comportamiento principal de operación seguirá siendo el mismo, con pocos errores que de costumbre.

La solución alternativa propuesta (usar estructuras durante la fase de depuración y flotantes reales cuando están optimizadas) es interesante pero tiene inconvenientes: aumenta la probabilidad de tener errores solo en la versión optimizada. Y la aplicación optimizada de depuración es muy costosa.

Se puede implementar una buena propuesta para la demanda inicial de @Rocketmagnet para tipos de enteros usando:

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

El nivel de error será bastante alto, como el truco de un solo miembro, pero el compilador tratará esos tipos exactamente como los enteros integrados (incluida la capacidad de registro y la optimización), gracias por la alineación.

Pero este truco no se puede usar (tristemente) para números flotantes, y la mejor necesidad es, obviamente, la comprobación de dimensiones con valores reales. Uno no puede mezclar manzanas y peras: agregar longitud y área es un error común.

La invocación de Stroustrup por @Jerry es irrelevante. La virtualidad es significativa principalmente para la herencia pública, y la necesidad está aquí hacia la herencia privada. La consideración en torno a las reglas de conversión C "caóticas" (¿C ++ 14 tiene algo no caótico?) De tipo básico tampoco es útil: el objetivo es no tener reglas de conversión predeterminadas, no seguir reglas estándar.


Porque int es un tipo nativo y no una clase

Editar: moviendo mis comentarios a mi respuesta.

Viene del patrimonio C y lo que representan exactamente los primitivos. Una primitiva en c ++ es simplemente una colección de bytes que tienen poco significado excepto para el compilador. Una clase, por otro lado, tiene una tabla de funciones, y una vez que comienzas a recorrer la herencia y la ruta de herencia virtual, entonces tienes un vtable. Nada de eso está presente en una primitiva, y haciéndolo presente usted a) rompería un montón de código c que supone que un int es de solo 8 bytes yb) hará que los programas ocupen mucha más memoria.

Piénselo de otra manera. int / float / char no tiene ningún miembro o método de datos. Piensa en los primitivos como quarks: son los bloques de construcción que no puedes subdividir, los usas para hacer cosas más grandes (disculpas si mi analogía está un poco apagada, no sé suficiente física de partículas).



Si el OP realmente quiere entender POR QUÉ C ++ es como es, entonces debe obtener una copia del libro de Stroustup "El diseño y la evolución de C ++". Explica el fundamento de esta y muchas otras decisiones de diseño en los primeros días de C ++.


Si mal no recuerdo, esta fue la principal, o una de las principales razones por las que C ++ no se consideró un verdadero lenguaje orientado a objetos. La gente de Java diría: "En Java, TODO es un objeto";)


This is related to how the items are stored in memory. An int in C++ is an integral type, as mentioned elsewhere, and is just 32 or 64 bits (a word) in memory. An object, however, is stored differently in memory. It is usually stored on the heap, and it has functionality related to polymorphism.

I don''t know how to explain it any better. How would you inherit from the number 4?


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

Actuación

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.