Pregunta rápida: desde el punto de vista del diseño, ¿por qué, en C ++, no hay una clase base madre de todos, lo que suele ocurrir object
en otros lenguajes?
typedef
. Con una jerarquía de clases más genérica, podría usar object
o iterator
.
Pregunta rápida: desde el punto de vista del diseño, ¿por qué, en C ++, no hay una clase base madre de todos, lo que suele ocurrir object
en otros lenguajes?
typedef
. Con una jerarquía de clases más genérica, podría usar object
o iterator
.
Respuestas:
La decisión definitiva se encuentra en las preguntas frecuentes de Stroustrup . En resumen, no transmite ningún significado semántico. Tendrá un costo. Las plantillas son más útiles para los contenedores.
¿Por qué C ++ no tiene un objeto de clase universal?
No necesitamos uno: la programación genérica proporciona alternativas seguras de tipo estático en la mayoría de los casos. Otros casos se manejan usando herencia múltiple.
No existe una clase universal útil: un universal verdaderamente no tiene semántica propia.
Una clase "universal" fomenta el pensamiento descuidado sobre tipos e interfaces y conduce a una comprobación excesiva del tiempo de ejecución.
El uso de una clase base universal implica un costo: los objetos deben asignarse en montón para que sean polimórficos; eso implica costo de memoria y acceso. Los objetos de montón no admiten naturalmente la semántica de copia. Los objetos de montón no admiten el comportamiento de ámbito simple (lo que complica la gestión de recursos). Una clase base universal fomenta el uso de dynamic_cast y otras comprobaciones en tiempo de ejecución.
Objects must be heap-allocated to be polymorphic
- No creo que esta afirmación sea correcta en general. Definitivamente puede crear una instancia de una clase polimórfica en la pila y pasarla como un puntero a una de sus clases base, lo que produce un comportamiento polimórfico.
Foo
en una referencia a Bar
cuando Bar
no se hereda Foo
, se lanza de forma determinista una excepción. En C ++, intentar lanzar un puntero a Foo en un puntero a Bar podría enviar un robot atrás en el tiempo para matar a Sarah Connor.
Primero pensemos en por qué querrías tener una clase base en primer lugar. Puedo pensar en algunas razones diferentes:
Estas son las dos buenas razones por las que los lenguajes de las marcas Smalltalk, Ruby y Objective-C tienen clases base (técnicamente, Objective-C no tiene realmente una clase base, pero para todos los efectos, la tiene).
Para el n. ° 1, la necesidad de una clase base que unifique todos los objetos bajo una sola interfaz se evita con la inclusión de plantillas en C ++. Por ejemplo:
void somethingGeneric(Base);
Derived object;
somethingGeneric(object);
es innecesario, cuando se puede mantener la integridad del tipo por completo mediante polimorfismo paramétrico.
template <class T>
void somethingGeneric(T);
Derived object;
somethingGeneric(object);
Para el n. ° 2, mientras que en Objective-C, los procedimientos de administración de memoria son parte de la implementación de una clase y se heredan de la clase base, la administración de memoria en C ++ se realiza utilizando composición en lugar de herencia. Por ejemplo, puede definir un contenedor de puntero inteligente que realizará el recuento de referencias en objetos de cualquier tipo:
template <class T>
struct refcounted
{
refcounted(T* object) : _object(object), _count(0) {}
T* operator->() { return _object; }
operator T*() { return _object; }
void retain() { ++_count; }
void release()
{
if (--_count == 0) { delete _object; }
}
private:
T* _object;
int _count;
};
Luego, en lugar de llamar a métodos en el objeto en sí, estaría llamando a métodos en su contenedor. Esto no solo permite una programación más genérica: también le permite separar preocupaciones (ya que, idealmente, su objeto debería estar más preocupado por lo que debería hacer que por cómo debería gestionarse su memoria en diferentes situaciones).
Por último, en un lenguaje que tiene tanto primitivas como objetos reales como C ++, se pierden los beneficios de tener una clase base (una interfaz consistente para cada valor), ya que entonces tienes ciertos valores que no pueden ajustarse a esa interfaz. Para usar primitivas en ese tipo de situación, debe convertirlas en objetos (si su compilador no lo hace automáticamente). Esto crea muchas complicaciones.
Entonces, la respuesta corta a su pregunta: C ++ no tiene una clase base porque, al tener polimorfismo paramétrico a través de plantillas, no es necesario.
object
( System.Object
), pero no es necesario. Para el compilador, int
y System.Int32
son alias y pueden usarse indistintamente; el tiempo de ejecución maneja el boxeo cuando es necesario.
std::shared_ptr
que debería usarse en su lugar.
El paradigma dominante para las variables de C ++ es pasar por valor, no pasar por referencia. Forzar que todo se derive de una raíz Object
haría que pasarlos por valor fuera un error ipse facto.
(Porque aceptar un Objeto por valor como parámetro, por definición lo cortaría y quitaría su alma).
Esto no es bienvenido. C ++ te hace pensar si querías valor o semántica de referencia, dándote la opción. Esto es muy importante en la informática de rendimiento.
Object
sería relativamente pequeño.
¡El problema es que existe un tipo de este tipo en C ++! Lo es void
. :-) Cualquier puntero se puede convertir implícitamente de forma segura void *
, incluidos los punteros a tipos básicos, clases sin tabla virtual y clases con tabla virtual.
Dado que debería ser compatible con todas esas categorías de objetos, por void
sí mismo no puede contener métodos virtuales. Sin funciones virtuales y RTTI, no se puede obtener información útil sobre el tipo void
(coincide con CADA tipo, por lo que solo puede decir cosas que son verdaderas para CADA tipo), pero las funciones virtuales y RTTI harían que los tipos simples fueran muy ineficaces y evitarían que C ++ se un lenguaje adecuado para programación de bajo nivel con acceso directo a memoria, etc.
Entonces, existe ese tipo. Simplemente proporciona una interfaz muy minimalista (de hecho, vacía) debido a la naturaleza de bajo nivel del lenguaje. :-)
void
.
void
, no se compilará y obtendrá este error . En Java, podría tener una variable de tipo Object
y funcionaría. Esa es la diferencia entre void
un tipo "real". Quizás sea cierto que void
es el tipo base para todo, pero como no proporciona ningún constructor, método o campo, no hay forma de saber si está ahí o no. Esta afirmación no se puede probar ni refutar.
C ++ es un lenguaje fuertemente tipado. Sin embargo, es sorprendente que no tenga un tipo de objeto universal en el contexto de la especialización de plantillas.
Tomemos, por ejemplo, el patrón
template <class T> class Hook;
template <class ReturnType, class ... ArgTypes>
class Hook<ReturnType (ArgTypes...)>
{
...
ReturnType operator () (ArgTypes... args) { ... }
};
que se puede instanciar como
Hook<decltype(some_function)> ...;
Ahora supongamos que queremos lo mismo para una función en particular. Me gusta
template <auto fallback> class Hook;
template <auto fallback, class ReturnType, class ... ArgTypes>
class Hook<ReturnType fallback(ArgTypes...)>
{
...
ReturnType operator () (ArgTypes... args) { ... }
};
con la instanciación especializada
Hook<some_function> ...
Pero, por desgracia, aunque la clase T puede representar cualquier tipo (clase o no) antes de la especialización, no hay equivalente auto fallback
(estoy usando esa sintaxis como el no tipo genérico más obvio en este contexto) que pueda reemplazar a cualquier argumento de plantilla sin tipo antes de la especialización.
Entonces, en general, este patrón no se transfiere de argumentos de plantilla de tipo a argumentos de plantilla que no son de tipo.
Al igual que con muchos rincones en el lenguaje C ++, la respuesta probablemente sea "ningún miembro del comité pensó en eso".
ReturnType fallback(ArgTypes...)
funciona, sería un mal diseño. template <class T, auto fallback> class Hook; template <class ReturnType, class ... ArgTypes> class Hook<ReturnType (ArgTypes...), ReturnType(*fallback)(ArgTypes...)> ...
hace lo que quiere y significa que los parámetros de la plantilla Hook
son de tipos
C ++ se llamó inicialmente "C con clases". Es una progresión del lenguaje C, a diferencia de otras cosas más modernas como C #. Y no se puede ver C ++ como un lenguaje, sino como una base de lenguajes (Sí, me acuerdo del libro de Scott Meyers Effective C ++).
C en sí mismo es una mezcla de lenguajes, el lenguaje de programación C y su preprocesador.
C ++ agrega otra combinación:
el enfoque de clase / objetos
plantillas
el STL
Personalmente, no me gustan algunas cosas que vienen directamente de C a C ++. Un ejemplo es la función de enumeración. La forma en que C # permite que el desarrollador lo use es mucho mejor: limita la enumeración en su propio alcance, tiene una propiedad Count y es fácilmente iterable.
Como C ++ quería ser retrocompatible con C, el diseñador fue muy permisivo al permitir que el lenguaje C ingresara en su totalidad a C ++ (hay algunas diferencias sutiles, pero no recuerdo nada que pudieras hacer usando un compilador de C que no podría hacer usando un compilador de C ++).