2011-03-30 20 views
5

Tengo clase C++ con múltiples padres; cada padre define una función con un nombre común, pero con un propósito diferente:Anulación de métodos virtuales calificados

class BaseA 
{ 
    virtual void myFunc(); // does some task 
}; 
class BaseB 
{ 
    virtual void myFunc(); // does some other task 
}; 
class Derived : public BaseA, public BaseB; 

Si eso fue todo, yo no tendría ningún problema - que podía resolver la ambigüedad con un usando declaración, y pude elegir qué uno para llamar usando los nombres de las clases base y el operador de resolución del alcance.

Por desgracia, la clase derivada tiene que reemplazar a los dos:

class Derived : public BaseA, public BaseB 
{ 
    virtual void BaseA::myFunc(); // Derived needs to change the way both tasks are done 
    virtual void BaseB::myFunc(); 
} 

Esto no funciona, no porque se introduce una nueva ambigüedad (aunque puede), sino porque

" error C3240: 'myFunc': debe ser una función miembro no sobrecargada de 'BaseA' "

" error C2838: nombre calificado ilegal en la declaración de miembro "

Bajo circunstancias diferentes, podría simplemente cambiar el nombre de los métodos, o hacerlos puros virtuales como sugiere el compilador. Sin embargo, la estructura jerárquica de clase y una serie de problemas externos hacen que la primera opción sea extremadamente difícil y la segunda imposible.

¿Alguien tiene alguna sugerencia? ¿Por qué los calificadores solo están permitidos para métodos virtuales puros? ¿Hay alguna manera de anular simultáneamente los métodos virtuales y resolver ambigüedades?

+0

También podría anular los comportamientos en clases separadas: 'DerivedA: public BaseA',' DerivedB: public BaseB', y luego 'Derived: public DerivedA, public DerivedB'. Esto no resuelve el problema de ambigüedad, aunque – Cameron

Respuesta

3

Microsoft allows that syntax (está disponible a partir de Visual C++ 2005). They also introduced a new, more powerful syntax for managed code only.

Ninguno de los dos se incluyó en C++ 0x.

Ver http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2108.html


Creo que esta es una solución:

class BaseA 
{ 
protected: 
    virtual void myFunc(); // does some task 
}; 
class BaseB 
{ 
protected: 
    virtual void myFunc(); // does some other task 
}; 
class ShimA : virtual BaseA 
{ 
    virtual void myFunc() { myFuncA(); } 
protected: 
    virtual void myFuncA() { BaseA::myFunc(); } 
}; 
class ShimB : virtual BaseB 
{ 
    virtual void myFunc() { myFuncB(); } 
protected: 
    virtual void myFuncB() { BaseB::myFunc(); } 
}; 
class Derived : public virtual BaseA, public virtual BaseB, protected ShimA, protected ShimB 
{ 
    virtual void myFuncA() {} 
    virtual void myFuncB() {} 
}; 
+0

Gracias por la información sobre la sintaxis de MS; desafortunadamente, solo funciona con clases base abstractas (observe la palabra clave "__interface" en su ejemplo). – John

+0

@John: la variante gestionada funciona para métodos virtuales heredados de clases base. De todos modos, eche un vistazo a mi edición, creo que esto debería resolver su problema. –

+0

Gracias de nuevo. Su sugerencia funcionaría, pero los detalles de mi situación me impiden usarla. Básicamente, estoy escribiendo una definición de clase para algunos códigos de ingeniería inversa y literalmente no puedo cambiar la estructura de herencia o la composición de la tabla de funciones virtuales (o usar C++ administrado, obviamente) – John

1

Este es uno de los grandes problemas con multiple inheritance. Siempre hay problemas cuando se heredan múltiples funciones del mismo nombre para determinar cuál se debe sobrescribir, ver The Diamond Problem. No puede anular ambos ya que la sintaxis de la función (nombre de la función y operadores) debe ser única.

1

Puede usar un objeto de composición.

class Derived : public BaseB {   
    struct temp : public BaseA { 
     virtual void myFunc() { 
      d->BaseAMyFunc(); 
     } 
     Derived* d; 
    }; 
    temp t; 
public: 
    Derived() { 
     t.d = this; 
    } 
    operator BaseA&() { return temp; } 
    operator const BaseA&() const { return temp; } 
    void myFunc(); // refers to BaseB::myFunc() 
    void BaseAMyFunc(); // called when BaseA::myFunc() is called. 
} 

Esto no es especialmente limpio y es algo restringido, pero funciona en algunas circunstancias.

0

Soy consciente de que esta pregunta es viejo, pero que ha tenido una gran cantidad de puntos de vista y no hay una forma limpia para resolver esto si eres el autor de las interfaces.

Muchas personas creen que las interfaces virtuales deben tener funciones públicas no virtuales que difieren internamente de las funciones virtuales privadas (estoy de acuerdo con ellas).Esto tiene algunas ventajas, una de ellas es que los nombres no virtuales pueden tener significados distintos, ya que están más fuertemente ligados a la interfaz:

struct BaseA 
{ 
    virtual ~BaseA() = default; 

    void func() 
    { 
    handle_a_func(); 
    } 

private: 
    virtual void handle_a_func() = 0; 
}; 

struct BaseB 
{ 
    virtual ~BaseB() = default; 

    int func() const // note the different signature and return type 
    { 
    handle_b_func(); 
    } 

private: 
    virtual int handle_b_func() const = 0; 
}; 

// now provide an implementation 

struct ABImpl : public BaseA, public BaseB 
{ 
    ABImpl() {} 

private: 
    void handle_a_func() override final 
    { 
    // alter some state 
    } 

    int handle_b_func() const override final 
    { 
    return _x; 
    } 

    int _x = 0; 
};   

// now use the class 
auto ab = make_shared<ABImpl>(); 

auto a = static_pointer_cast<BaseA>(ab); 
auto b = static_pointer_cast<const BaseB>(ab); 

a->func(); // uses A's mutable interface 
auto x = b->func(); // uses B's const interface 

Otra ventaja de este enfoque es que las implementaciones de la clase base pueden manejar cosas como mutexes y centinelas en nombre de las funciones derivadas, por ejemplo:

struct base { 

    void do() { 
    std::unique_lock<std::mutex> l(_m); 
    handle_do(); 
    } 

private: 
    virtual void handle_do() = 0; 

    std::mutex _m; 
}; 

Aún otra ventaja es que algunos operadores de libre función útil sólo necesitan ser definidas una vez para toda la jerarquía de clases:

struct base 
{ 
    void write_to(std::ostream& os) const { 
    // lock a mutex here? 
    handle_write(os); 
    private: 
    virtual void handle_write(std::ostream& os) const { 
     os << "write base class info here\n"; 
    } 
}; 

inline std::ostream& operator<<(std::ostream& os, const base& b) { 
    b.write_to(os); 
    return os; 
} 

struct derived : base { 
private: 
    virtual void handle_write(std::ostream& os) const override { 
    base::handle_write(os); // handle base class implementation 
    std::cout << "write relevant data here. We could still be overridden safely\n"; 
    } 
}; 

// write any derived class like this: 
auto b = unique_ptr<base> { new derived() }; 
cout << "here is a kind-of b:\n" << *b << endl;