Pregunta

Recibí esta pregunta cuando recibí un comentario de revisión de código que decía que las funciones virtuales no necesitan estar en línea.

Pensé que las funciones virtuales en línea podrían resultar útiles en escenarios en los que las funciones se llaman directamente sobre objetos.Pero el contraargumento que me vino a la mente es: ¿por qué querría uno definir virtual y luego usar objetos para llamar a métodos?

¿Es mejor no utilizar funciones virtuales en línea, ya que de todos modos casi nunca se expanden?

Fragmento de código que utilicé para el análisis:

class Temp
{
public:

    virtual ~Temp()
    {
    }
    virtual void myVirtualFunction() const
    {
        cout<<"Temp::myVirtualFunction"<<endl;
    }

};

class TempDerived : public Temp
{
public:

    void myVirtualFunction() const
    {
        cout<<"TempDerived::myVirtualFunction"<<endl;
    }

};

int main(void) 
{
    TempDerived aDerivedObj;
    //Compiler thinks it's safe to expand the virtual functions
    aDerivedObj.myVirtualFunction();

    //type of object Temp points to is always known;
    //does compiler still expand virtual functions?
    //I doubt compiler would be this much intelligent!
    Temp* pTemp = &aDerivedObj;
    pTemp->myVirtualFunction();

    return 0;
}
¿Fue útil?

Solución

funciones virtuales pueden ser inline veces. Un extracto de la excelente C ++ FAQ :

  

"La única vez que un llamada virtual en línea   puede ser inline es cuando el compilador   conoce la "clase exacta" del objeto   que es el objetivo de lo virtual   Llamada de función. Esto sólo puede ocurrir   cuando el compilador tiene un objeto real   en lugar de un puntero o referencia a   un objeto. Es decir, ya sea con un local   objeto, un objeto global / estática, o una   contenida totalmente dentro de un objeto   compuesto ".

Otros consejos

C ++ 11 ha añadido final. Esto cambia la respuesta aceptada: ya no es necesario conocer la clase exacta del objeto, es suficiente conocer el objeto tiene al menos el tipo de clase en el que la función fue declarada definitiva:

class A { 
  virtual void foo();
};
class B : public A {
  inline virtual void foo() final { } 
};
class C : public B
{
};

void bar(B const& b) {
  A const& a = b; // Allowed, every B is an A.
  a.foo(); // Call to B::foo() can be inlined, even if b is actually a class C.
}

Hay una categoría de funciones virtuales donde todavía tiene sentido tener ellos en línea. Considere el siguiente caso:

class Base {
public:
  inline virtual ~Base () { }
};

class Derived1 : public Base {
  inline virtual ~Derived1 () { } // Implicitly calls Base::~Base ();
};

class Derived2 : public Derived1 {
  inline virtual ~Derived2 () { } // Implicitly calls Derived1::~Derived1 ();
};

void foo (Base * base) {
  delete base;             // Virtual call
}

La llamada a borrar 'base', llevará a cabo una llamada virtual para llamar correcta destructor clase derivada, esta llamada no se colocarán en línea. Sin embargo, ya que cada destructor llama es destructor de los padres (que en estos casos están vacíos), el compilador puede inline los llamadas, ya que no llaman a las funciones de la clase base virtual.

Existe el mismo principio para los constructores de clase base o para cualquier conjunto de funciones cuando la ejecución derivada también llama la implementación clases base.

He visto compiladores que no emiten ninguna tabla v si no existe ninguna función no en línea (y luego se definen en un archivo de implementación en lugar de un encabezado).Tirarían errores como missing vtable-for-class-A o algo similar, y estarías tan confundido como yo.

De hecho, eso no es conforme con el Estándar, pero sucede, así que considere colocar al menos una función virtual que no esté en el encabezado (aunque solo sea el destructor virtual), para que el compilador pueda emitir una tabla virtual para la clase en ese lugar.Sé que sucede con algunas versiones de gcc.

Como alguien mencionó, las funciones virtuales en línea pueden ser un beneficio a veces, pero por supuesto la mayoría de las veces lo usarás cuando lo hagas. no conocer el tipo dinámico del objeto, porque esa fue la única razón para virtual en primer lugar.

Sin embargo, el compilador no puede ignorar por completo inline.Tiene otra semántica además de acelerar una llamada a función.El implícito en línea para definiciones en clase es el mecanismo que le permite poner la definición en el encabezado:Solo inline Las funciones se pueden definir varias veces a lo largo de todo el programa sin violar ninguna regla.Al final, se comporta como lo habrías definido solo una vez en todo el programa, aunque hayas incluido el encabezado varias veces en diferentes archivos vinculados entre sí.

Bueno, en realidad funciones virtuales pueden siempre ser inline , en tanto que están unidos entre sí de forma estática: supongamos que tenemos una clase abstracta Base con un F función virtual y las clases derivadas Derived1 y Derived2:

class Base {
  virtual void F() = 0;
};

class Derived1 : public Base {
  virtual void F();
};

class Derived2 : public Base {
  virtual void F();
};

Una llamada b->F(); hipotética (con b de tipo Base*) es obviamente virtual. Sin embargo, usted (o el compilador ...) podría volver a escribir como tal (supongamos typeof es una función typeid-como que devuelve un valor que se puede utilizar en un switch)

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // static, inlineable call
  case Derived2: b->Derived2::F(); break; // static, inlineable call
  case Base:     assert(!"pure virtual function call!");
  default:       b->F(); break; // virtual call (dyn-loaded code)
}

A pesar de que todavía necesitamos RTTI para la typeof, la llamada puede efectivamente ser inline por, básicamente, la incorporación de la viable dentro del flujo de instrucciones y especializada la convocatoria de todas las clases involucradas. Esto podría también ser generalizada mediante la especialización sólo unas pocas clases (digamos, simplemente Derived1):

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // hot path
  default:       b->F(); break; // default virtual call, cold path
}

Marcado de un método en línea virtual, ayuda a optimizar aún más funciones virtuales en los siguientes dos casos:

línea realmente no hace nada - es un indicio. El compilador puede ignorarlo o podría inline un evento de llamada sin línea si se ve la aplicación y le gusta esta idea. Si la claridad del código que está en juego el línea debe ser eliminado.

inlined declaró funciones virtuales están entre líneas cuando se llama a través de objetos y se ignoran cuando se llama a través de puntero o referencias.

Con los compiladores modernos, no va a hacer ningún daño a inlibe ellos. Algunos combos antigua compilador / enlazador podrían haber creado múltiples vtables, pero no creen que es un problema más.

En los casos en que la llamada de función no es ambigua y la función de un candidato adecuado para procesos en línea, el compilador es suficientemente inteligente como para inline el código de todos modos.

El resto del tiempo "en línea virtual" es un disparate, y de hecho algunos compiladores no se compilará el código.

Un compilador sólo puede inline una función cuando la llamada se puede resolver de forma inequívoca en tiempo de compilación.

funciones virtuales, sin embargo se resuelven en tiempo de ejecución, por lo que el compilador no puede inline la llamada, ya que durante la compilación escriba el tipo dinámico (y por lo tanto la implementación de la función de ser llamado) no puede ser determinado.

Tiene sentido para hacer las funciones virtuales y luego llamarlos en objetos en lugar de referencias o punteros. Scott Meyer recomienda, en su libro "c ++ efectiva", para que nunca redefinir una función no virtual heredada. Eso tiene sentido, porque cuando haces una clase con una función no virtual y volver a definir la función en una clase derivada, puede estar seguro de usar correctamente a sí mismo, pero no se puede estar seguro de que otros utilizarlo correctamente. Además, es posible que en una fecha posterior usarlo incorrectamente yoruself. Por lo tanto, si usted hace una función en una clase base y desea que sea redifinable, usted debe hacer que sea virtual. Si tiene sentido para hacer las funciones virtuales y llamarlos en los objetos, sino que también tiene sentido para ellos inline.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top