La creación de instancias de objeto de función con diferentes definiciones de función en línea depende del orden de enlace

StackOverflow https://stackoverflow.com/questions/1807523

  •  05-07-2019
  •  | 
  •  

Pregunta

Por favor, ayúdame a entender la causa raíz del siguiente comportamiento.

En el archivo a.cpp tengo:

namespace NS {
   struct Obj {
      void pong(){ cout << "X in "__FILE__ << endl; }
      double k;
   };
   X::X() { Obj obj; obj.pong(); }
   void X::operator()() { cout << "X says hello" << endl; }
}

En el archivo b.cpp tengo:

namespace NS {
   struct Obj {
      void pong(){ cout << "Y in "__FILE__ << endl; }
      bool m;
   };
   Y::Y() { Obj obj; obj.pong(); }
   void Y::operator()() { cout << "Y says hello" << endl; }
}

Mi main crea una X, una Y y llama a su operador () s:

int main( int argc, char *argv[] )
{
   NS::X x;
   x();

   NS::Y y;
   y();

   return 0;
}

La salida de este programa depende de si primero se compila a.cpp o b.cpp : en el primer caso, el Obj de a.cpp también se crea una instancia dentro del constructor de NS :: Y , en el segundo caso, el Obj de b.cpp se crea una instancia tanto en NS :: X como en NS :: Y .

% g++ b.cpp a.cpp main.cpp
% ./a.out

X in a.cpp
X says hello
Y in b.cpp
Y says hello

% g++ b.cpp a.cpp main.cpp
% ./a.out

Y in b.cpp
X says hello
Y in b.cpp
Y says hello

No hay advertencias del vinculador en Linux o Visual Studio (2005). Si defino Obj :: pong () fuera de la declaración de la estructura, obtengo un error de enlace que me indica que la función Obj :: pong está definida de manera múltiple.

Experimenté un poco más y descubrí que la causa debe estar relacionada con si la alineación o no, porque si compilo con -O3, cada objeto usa el Obj de su propia unidad de traducción.

Entonces, la pregunta cambia a: ¿qué sucede con la segunda definición de la función en línea durante la compilación no optimizada? ¿Son ignorados silenciosamente?

¿Fue útil?

Solución

Este es un comportamiento indefinido: las definiciones de su clase definen el mismo tipo de clase, y por lo tanto tienen que ser las mismas. Para el enlazador significa que puede elegir una definición arbitraria como la que se emite.

Si desea que sean tipos separados, debe anidarlos en un espacio de nombre sin nombre. Esto hará que cualquier cosa en ese espacio de nombres sea única para esa unidad de traducción:

namespace NS {
   namespace {
   struct Obj {
      void pong(){ cout << "Y in "__FILE__ << endl; }
      bool m;
   };
   }
   Y::Y() { Obj obj; obj.pong(); }
   void Y::operator()() { cout << "Y says hello" << endl; }
}
  

Entonces, la pregunta cambia a: ¿qué sucede con la segunda definición de la función en línea durante la compilación no optimizada? ¿Son ignorados silenciosamente?

Sí, para las funciones en línea (las funciones definidas dentro de las definiciones de clase están en línea, incluso si no se declaran explícitamente en línea), se aplica el mismo principio: se pueden definir varias veces en el programa, y ??el programa se comporta como si solo se definiera una vez. Para el enlazador significa de nuevo que puede descartar todas las definiciones menos una. La que elija no está especificada.

Otros consejos

El enlazador se ocupa de nombres destrozados. Por favor, eche un vistazo aquí: http://en.wikipedia.org/wiki/Name_mangling

Entonces, como dijo Johannes, el comportamiento no está definido, pero los detalles pueden aclararse:
Si pong () se define fuera del espacio de nombres, su nombre se convierte en único y el vinculador se queja correctamente.

Pero si el nombre está oculto en el espacio de nombres, que se superpone con el mismo de otra unidad de traducción, como ya descubrió, el vinculador no se queja. En su lugar, solo usa un símbolo.
Eso es.

Creo que no está especificado y es específico de la implementación para cualquier compilador / enlazador.

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