Creo que entiendo las limitaciones reales del polimorfismo en tiempo de compilación y el polimorfismo en tiempo de ejecución. Pero, ¿cuáles son las diferencias conceptuales entre interfaces explícitas (polimorfismo en tiempo de ejecución, es decir, funciones virtuales y punteros / referencias) e interfaces implícitas (polimorfismo en tiempo de compilación, es decir, plantillas) .
Mi opinión es que dos objetos que ofrecen la misma interfaz explícita deben ser del mismo tipo de objeto (o tener un ancestro común), mientras que dos objetos que ofrecen la misma interfaz implícita no necesitan ser el mismo tipo de objeto y, excluyendo el implícito La interfaz que ambos ofrecen puede tener una funcionalidad bastante diferente.
Tiene alguna idea sobre esto?
Y si dos objetos ofrecen la misma interfaz implícita, qué razones (además del beneficio técnico de no necesitar un despacho dinámico con una tabla de búsqueda de funciones virtuales, etc.) están ahí para no tener estos objetos heredados de un objeto base que declara esa interfaz, por lo tanto convirtiéndolo en una interfaz explícita ? Otra forma de decirlo: ¿puede darme un caso en el que dos objetos que ofrecen la misma interfaz implícita (y, por lo tanto, se pueden usar como tipos para la clase de plantilla de muestra) no deberían heredar de una clase base que hace explícita esa interfaz?
Algunas publicaciones relacionadas:
- https://stackoverflow.com/a/7264550/635125
- https://stackoverflow.com/a/7264689/635125
- https://stackoverflow.com/a/8009872/635125
Aquí hay un ejemplo para hacer esta pregunta más concreta:
Interfaz implícita
class Class1
{
public:
void interfaceFunc();
void otherFunc1();
};
class Class2
{
public:
void interfaceFunc();
void otherFunc2();
};
template <typename T>
class UseClass
{
public:
void run(T & obj)
{
obj.interfaceFunc();
}
};
Interfaz explícita
class InterfaceClass
{
public:
virtual void interfaceFunc() = 0;
};
class Class1 : public InterfaceClass
{
public:
virtual void interfaceFunc();
void otherFunc1();
};
class Class2 : public InterfaceClass
{
public:
virtual void interfaceFunc();
void otherFunc2();
};
class UseClass
{
public:
void run(InterfaceClass & obj)
{
obj.interfaceFunc();
}
};
Un ejemplo aún más profundo y concreto:
Algunos problemas de C ++ se pueden resolver con:
- Una clase con plantilla cuyo tipo de plantilla proporciona una interfaz implícita
- una clase sin plantilla que toma un puntero de clase base que proporciona una interfaz explícita
Código que no cambia:
class CoolClass
{
public:
virtual void doSomethingCool() = 0;
virtual void worthless() = 0;
};
class CoolA : public CoolClass
{
public:
virtual void doSomethingCool()
{ /* Do cool stuff that an A would do */ }
virtual void worthless()
{ /* Worthless, but must be implemented */ }
};
class CoolB : public CoolClass
{
public:
virtual void doSomethingCool()
{ /* Do cool stuff that a B would do */ }
virtual void worthless()
{ /* Worthless, but must be implemented */ }
};
Caso 1 . Una clase sin plantilla que toma un puntero de clase base que proporciona una interfaz explícita:
class CoolClassUser
{
public:
void useCoolClass(CoolClass * coolClass)
{ coolClass.doSomethingCool(); }
};
int main()
{
CoolA * c1 = new CoolClass;
CoolB * c2 = new CoolClass;
CoolClassUser user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
Caso 2 . Una clase con plantilla cuyo tipo de plantilla proporciona una interfaz implícita:
template <typename T>
class CoolClassUser
{
public:
void useCoolClass(T * coolClass)
{ coolClass->doSomethingCool(); }
};
int main()
{
CoolA * c1 = new CoolClass;
CoolB * c2 = new CoolClass;
CoolClassUser<CoolClass> user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
Caso 3 . Una clase con plantilla cuyo tipo de plantilla proporciona una interfaz implícita (esta vez, no derivada de CoolClass
:
class RandomClass
{
public:
void doSomethingCool()
{ /* Do cool stuff that a RandomClass would do */ }
// I don't have to implement worthless()! Na na na na na!
}
template <typename T>
class CoolClassUser
{
public:
void useCoolClass(T * coolClass)
{ coolClass->doSomethingCool(); }
};
int main()
{
RandomClass * c1 = new RandomClass;
RandomClass * c2 = new RandomClass;
CoolClassUser<RandomClass> user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
El caso 1 requiere que el objeto que se pasa useCoolClass()
sea hijo de CoolClass
(e implemente worthless()
). Los casos 2 y 3, por otro lado, tomarán cualquier clase que tenga una doSomethingCool()
función.
Si los usuarios del código siempre estuvieran bien subclasificados CoolClass
, entonces el Caso 1 tiene un sentido intuitivo, ya CoolClassUser
que siempre se esperaría una implementación de a CoolClass
. Pero suponga que este código será parte de un marco de API, por lo que no puedo predecir si los usuarios querrán subclasificar CoolClass
o rodar su propia clase que tiene una doSomethingCool()
función.
fuente
Respuestas:
Ya ha definido el punto importante: uno es el tiempo de ejecución y el otro es el tiempo de compilación . La información real que necesita son las ramificaciones de esta elección.
Tiempo de compilación:
virtual
herencia u otras travesuras con interfaces implícitas, una gran ventaja.Tiempo de ejecución:
Dada la lista relativa, si no necesita una ventaja específica de la herencia en tiempo de ejecución, no la use. Es más lento, menos flexible y menos seguro que las plantillas.
Editar: Vale la pena señalar que en C ++ particularmente hay usos para la herencia que no sean el polimorfismo en tiempo de ejecución. Por ejemplo, puede heredar typedefs, o usarlo para el etiquetado de tipos, o usar el CRTP. Sin embargo, en última instancia, estas técnicas (y otras) realmente se incluyen en "Tiempo de compilación", a pesar de que se implementan utilizando
class X : public Y
.fuente