2011-02-01 15 views
7

Supongamos que tengo una clase base abstracta, que simplemente define un recipiente en el que se puede realizar además:¿Cuál es la forma correcta de sobrecargar a los operadores en clases base abstractas?

class Base { 
public: 
    virtual ~Base() {} 
    virtual Base operator+(const Base& rhs) =0; 
}; 

entonces quiero subclases de base para proporcionar la operación real:

class Derived: public Base { 
public: 
    Base operator+(const Base& rhs) { // won't compile 
     // actual implementation 
    } 
}; 

Aquí está mi problema: operador +() se supone que devuelve un nuevo objeto Base, pero como Base es abstracto, no se compilará.

Intenté evitarlo utilizando una fábrica para devolver una referencia a un objeto Base, pero luego en el cuerpo del operador me encuentro haciendo moldes, porque la adición solo tiene sentido en los objetos Derivados.

En cualquier caso, parece que me estoy mordiendo la cola, ¿hay una solución adecuada para esto?

ACTUALIZACIÓN: Basado en las respuestas hasta ahora, parece que estoy usando el patrón incorrecto. Quiero separar la interfaz de la implementación, por lo que el código de la biblioteca solo debe conocer la interfaz y el código del cliente proporciona la implementación. Traté de hacerlo al proporcionar la interfaz como una clase base abstracta y la implementación como subclases.

ACTUALIZACIÓN2: Mi pregunta era en realidad de 2 preguntas, una concreta (sobre operadores de sobrecarga en clases abstractas) y otra sobre mi intención (cómo le permití al cliente personalizar la implementación). El primero ha sido respondido: no. Para este último, parece que el patrón de clase de interfaz que uso es realmente bueno para resolver ese problema (de acuerdo con Griffiths and Radford), es solo que no debería meterme con los operadores sobrecargados.

+0

Ahh, un compañero /. lector – John

Respuesta

8

Lo mejor es no hacerlo.

operator+ devuelve un valor y no puede devolver un valor de tipo abstracto, por definición. Sobrecargue los operadores solo para tipos de concreto y evite heredar de los tipos de concreto para evitar "cortes por operador sobrecargado".

Sobrecargue los operadores binarios simétricos como operator+ como funciones libres y puede controlar qué combinaciones de tipos se pueden combinar sensiblemente y, a la inversa, evitar la combinación de objetos de los tipos para los que la combinación no tiene sentido.

Si tiene una forma válida de realizar un "agregar" a través de dos referencias de la clase base y crear un nuevo objeto, tendrá que volver a través de un puntero, referencia o objeto inteligente de ajuste del puntero. Como no puede preservar la semántica convencional de +, le recomendaría usar una función con nombre, p. Add() en lugar de hacer un operator+ con una sintaxis "sorprendente".

+0

Actualicé la pregunta para aclarar mi intención: el único caso donde la adición tiene sentido es para 2 objetos Derivados, pero el código de la biblioteca solo conoce la interfaz (es decir, Base), no la implementación (es decir, Derivada) – AnonymousCoward

+0

@AnonymousCoward: Tengo lee tu actualización, pero no creo que realmente afecte lo que he dicho. Si un cliente de la interfaz no tiene conocimiento de 'Derived', no puede recibir un nuevo objeto' Derived' por valor, por lo que debe usar punteros o referencias a 'Base'. –

+0

Bien, pero ¿cómo me aseguro de que la biblioteca llame a la función Add() agregada por el cliente si ya no puedo confiar en las funciones de miembros virtuales para hacerlo? – AnonymousCoward

1
template<class C> 
class Base { 
public: 
    virtual ~Base() {} 
    virtual C operator+(const C& rhs) =0; 
}; 

class Derived: public Base<Derived> { 
public: 

podría funcionar (salvo problema de clase imcomplete).

+1

En esta solución, cada clase derivada se deriva de una clase * diferente * Base <>, lo que es algo contrario a la idea de polimorfismo. – Amnon

+0

@Ammom sí, cierto. sin embargo, usé un truco similar algunas veces, aunque por una razón diferente. – Anycorn

0

Base siendo abstracto, no se puede crear una instancia, por lo que no se puede devolver por valor. Eso significa que tendrá que regresar por puntero o referencia. Volver por referencia es peligroso ya que es probable que operator+ devuelva un valor temporal.

Eso se va por puntero, pero eso sería muy extraño. Y está toda la cuestión de cómo liberar el puntero cuando ya no lo necesite. Usar un puntero inteligente podría ser una solución a ese problema, pero aún tiene todo el problema "operator+ devuelve un puntero".

Entonces, ¿qué estás tratando de lograr? ¿Qué representa Base? ¿Tiene sentido agregar dos instancias de una subclase Base?

+0

Actualicé la pregunta para aclarar mi intención – AnonymousCoward

2

La solución habitual a esto es el patrón de Jerarquía algebraica de Jim Coplein. Véase el siguiente:

Coplein's original paper (particularmente Algebraic Hierarchy)

Wikibooks

Para elaborar un poco, necesita esencialmente una clase de contenedor concreto que tiene un puntero polimórfico al objeto real de la clase derivada. Defina los operadores en cuestión para reenviar a la representación subyacente conservando la semántica de los valores (en lugar de la semántica de los objetos) que está buscando. Asegúrese de que la clase contenedora use la expresión idiomática RAII para no perder memoria con cada objeto temporal.

0

Quiero separar la interfaz de la implementación, por lo que el código de la biblioteca solo tiene que conocer la interfaz y el código del cliente proporciona la implementación. Traté de hacerlo al proporcionar la interfaz como una clase base abstracta y la implementación como subclases.

Uso del pimpl idiom:

struct Base { 
    virtual ~Base() = 0; 
    virtual std::auto_ptr<Base> clone() = 0; 
    virtual void add(Base const &x) = 0; // implements += 
}; 

struct UserInterface { 
    UserInterface() : _pimpl(SomeFactory()) {} 
    UserInterface(UserInterface const &x) : _pimpl(x._pimpl->clone()) {} 

    UserInterface& operator=(UserInterface x) { 
    swap(*this, x); 
    return *this; 
    } 

    UserInterface& operator+=(UserInterface const &x) { 
    _pimpl->add(*x._pimpl); 
    } 

    friend void swap(UserInterface &a, UserInterface &b) { 
    using std::swap; 
    swap(a._pimpl, b._pimpl); 
    } 

private: 
    std::auto_ptr<Base> _pimpl; 
}; 

UserInterface operator+(UserInterface a, UserInterface const &b) { 
    a += b; 
    return a; 
} 

Para implementar realmente Base :: añada, tendrá que o bien 1) el doble de despacho y hacer frente a la sobrecarga de la explosión resultante, 2) requieren que sólo una clase derivada de base se usa por ejecución de programa (aún usando pimpl como un firewall de compilación, por ejemplo, para intercambiar bibliotecas compartidas), o 3) requiere que las clases derivadas sepan cómo tratar con una base genérica o lanzarán una excepción.

-2

utilice el tipo dinámico y el tipo de base. en la clase base para enumerar la clase derivada es imposible ya que la sobrecarga del operador debe ser estática en todo momento. La opción más conveniente sería usar el tipo "dinámico" en los argumentos de sobrecarga. El siguiente es un ejemplo.

public class ModelBase 
{ 
    public abstract static string operator +(ModelBase obj1, dynamic obj2) 
    { 
     string strValue = ""; 
     dynamic obj = obj1; 
     strValue = obj.CustomerID + " : " + obj2.CustomerName; 
     return strValue; 
    } 
} 
+0

Esto solo está disponible en C++/CLI ¿no es así? –

Cuestiones relacionadas