2010-08-19 14 views
8

Solo una pequeña molestia realmente porque puedo evitar el problema envolviendo la función derivada en lugar de usar la palabra clave 'usar', pero ¿por qué el siguiente trabajo (el el compilador me dice que 'get_elem' sigue siendo puramente virtual en la clase 'Bar').Sobrescribir funciones virtuales puras al "usar" un método heredado por separado

class Elem {}; 

class DerivedElem : public Elem {}; 

class Foo { 

    public: 
    virtual Elem& get_elem() = 0; 

}; 

class Goo { 

    protected: 
    DerivedElem elem; 

    public: 
    DerivedElem& get_elem() { return elem; } 

}; 


class Bar : public Foo, public Goo { 

    public: 
    using Goo::get_elem; 

}; 

int main(void) { 

    Bar bar; 

} 

Cheers,

Tom

+4

Ejemplo ordenado, pequeño, autónomo. Deseo que todos los que tenían una pregunta sobre el código hicieran lo que hicieron. – Omnifarious

+0

¿No debería derivarse Goo de Foo para tipos de retorno covariantes? – Chubsdad

+0

Hola chicos, gracias por las respuestas rápidas y útiles. Buen consejo sobre la asignación polimórfica Steve. En caso de que alguien se preguntara por qué estoy mirando una estructura así, en mi código real 'Goo' es una clase de plantilla heredada por clases de plantilla subsiguientes y 'Foo' es una 'interfaz' para acceder a las clases de rango. – Tom

Respuesta

2

En el ejemplo, el Foo y Goo son clases separadas. En Bar, el método get_elem de Goo no es para nada igual al de Foo, incluso si coincide con la firma.

Al tener using Goo::get_elem, simplemente le dice al compilador que resuelva una llamada no calificada a get_elem() a la de Goo.

5

Si Goo es un "mixin" diseñado para implementar la interfaz Foo de una manera particular (podría haber otras mezclas con otras implementaciones), entonces Goo puede derivar de Foo (en lugar de hacerlo Bar).

Si Goo no está diseñado para implementar la interfaz Foo, sería un error terrible tratar a Bar como si hubiera implementado esa función virtual pura, cuando de hecho tiene una función de la misma firma . Si quieres interfaces implícitas y tipear "duck" en C++, puedes hacerlo, pero tienes que hacerlo con plantillas. Correcta o incorrectamente, las funciones virtuales puras son para las interfaces explícitamente declaradas, y la función get_elem de Goo no se declara explícitamente para implementar Foo::get_elem. Entonces no es así.

supongo que eso no explica por qué, en principio, el idioma no podía definir using Goo::get_elem for Foo;, o alguna declaración en la barra, para evitar la necesidad de una barra para contener una gran cantidad de texto modelo envolver la llamada.

Puede tal vez hacer algo con las plantillas para permitir Goo para apoyar esto en cierta medida, sin saber muy bien acerca de Foo:

template <typename T> 
class Goo : public T { 

    protected: 
    DerivedElem elem; 

    public: 
    DerivedElem& get_elem() { return elem; } 
}; 

class Bar : public Goo<Foo> {}; 

class Baz : public Goo<Fuu> {}; 

Dónde Fuu es alguna otra interfaz que tiene una función get_elem. Obviamente, es responsabilidad del autor de Bar asegurarse de que Goo realmente implemente el contrato de Foo, y lo mismo para Baz verificando el contrato de Fuu.

Por cierto, esta forma de covarianza es un poco dudosa. Al mirar a Foo, alguien podría esperar que la expresión bar.get_elem() = Elem() sea válida, y no lo es, por lo que se viola el LSP. Las referencias son divertidas así. ((Foo &)bar).get_elem() = Elem() es válido, pero en general no funciona! Solo se asigna al subobjeto Elem, y para el caso también lo hace ((Foo &)bar).get_elem() = DerivedElem(). La asignación polimórfica es básicamente una molestia.

1

Usted ha encontrado una de las esquinas extrañas de C++. En este caso, C++ no considera que dos funciones virtuales heredadas de diferentes clases sean la misma función aunque tengan el mismo nombre y firma de tipo.

Existen algunas buenas razones para que C++ actúe de esta manera. Por ejemplo, con frecuencia ocurre que esas dos funciones realmente no son lo mismo, a pesar de que tienen el mismo nombre y firma de tipo. El significado semántico de las dos funciones es diferente.

Aquí se muestra un ejemplo:

namespace vendor1 { 

class Circle { 
public: 
    virtual ::std::size_t size() const { return sizeof(*this); } 
}; 

} // namespace vendor1 


namespace vendor2 { 

class Shape { 
public: 
    virtual double size() const = 0; 
}; 

class Circle : public Shape { 
public: 
    virtual double size() const { return radius_ * radius_ * M_PI; } 
}; 

} // namespace vendor2 

a continuación, intenta esto:

namespace my_namespace { 

class Circle : public ::vendor1::Circle, public ::vendor2::Circle { 
// Oops, there is no good definition for size 
}; 

Así que hay que recurrir a esto:

namespace my_namespace { 

class Vendor1Circle : public ::vendor1::Circle { 
public: 
    virtual ::std::size_t data_structure_size() const { return size(); } 
}; 

class Vendor2Circle : public ::vendor2::Circle { 
public: 
    virtual double area() const { return size(); } 
}; 

class Circle : public Vendor1Circle, public Vendor2Circle { 
// Now size is still ambiguous and should stay that way 
// And in my opinion the compiler should issue a warning if you try 
// to redefine it 
}; 

Así, C++ tiene una buena razón tratar las funciones virtuales con la misma firma de tipo (el tipo de devolución no es parte de la firma de tipo) y el nombre de dos diferentes nt bases como funciones diferentes.

En cuanto a using va ... Toda una directiva using dice es "Agregue los nombres de este otro espacio de nombre a este espacio de nombres como si hubiera sido declarado aquí". Este es un concepto nulo en lo que respecta a las funciones virtuales. Simplemente sugiere que cualquier ambigüedad al usar un nombre debería resolverse de una manera diferente. Solo declara un nombre, no define el nombre. Para anular una función virtual, se requiere una nueva definición.

otoh, si se pone en un simple redefinición golpe seco en línea como esta:

class Bar : public Foo, public Goo { 

    public: 
    virtual DerivedElem& get_elem() { return Goo::get_elem(); } 
}; 

un buen compilador debe ver eso y saber que ni siquiera se molestan en crear la función, y en lugar de simplemente tocar el violín las entradas de la tabla virtuales para hacer lo correcto. Puede que necesite emitir código para ello y tener el símbolo disponible en caso de que se tome su dirección, pero aún así debería ser capaz de manipular la tabla virtual para que la función desaparezca por completo cuando se llame a través de Foo *.

Cuestiones relacionadas