2012-02-20 22 views
6

Consideremos el siguiente ejemplo:C++: Diferentes clases con el mismo nombre en diferentes unidades de traducción

// usedclass1.hpp 
#include <iostream> 
class UsedClass 
{ 
public: 
    UsedClass() { } 
    void doit() { std::cout << "UsedClass 1 (" << this << ") doit hit" << std::endl; } 
}; 

// usedclass2.hpp 
#include <iostream> 
class UsedClass 
{ 
public: 
    UsedClass() { } 
    void doit() { std::cout << "UsedClass 2 (" << this << ") doit hit" << std::endl; } 
}; 

// object.hpp 
class Object 
{ 
public: 
    Object(); 
}; 

// object.cpp 
#include "object.hpp" 
#include "usedclass2.hpp" 
Object::Object() 
{ 
    UsedClass b; 
    b.doit(); 
} 

// main.cpp 
#include "usedclass1.hpp" 
#include "object.hpp" 
int main() 
{ 
    Object obj; 
    UsedClass a; 
    a.doit(); 
} 

El código se compila sin ningún error de compilador o enlazador. Pero la salida es extraño para mí:

  • gcc (Red Hat 4.6.1-9) en Fedora x86_64 sin optimización [EG1]:

    UsedClass 1 (0x7fff0be4a6ff) doit golpe
    UsedClass 1 (0x7fff0be4a72e) doit golpeó

  • mismo que [EG1] pero con O2 opción activado [EG2]:

    UsedClass 2 (0x7fffcef79fcf) doit golpeó
    UsedClass 1 (0x7fffcef79fff) doit golpeó

  • msvc2005 (14.00.50727.762) en Windows XP de 32 bits sin optimización [EG3] :

    UsedClass 1 (0012FF5B) doit golpeó
    UsedClass 1 (0012FF67) doit golpeó

  • mismo que [EG3] pero con/O2 (o/Ox) activado [EG4]:

    UsedClass 1 (0012FF73) doit golpeó
    UsedClass 1 (0012FF7F) doit golpeó

yo esperaría que sea un error de vinculador (suponiendo que la regla es violada ODR) o la salida como en [EG2] (código se colocarán en línea, nada se exporta desde el translati en la unidad, se mantiene la regla de ODR). Por lo tanto, mis preguntas:

  1. ¿Por qué son posibles las salidas [EG1], [EG3], [EG4]?
  2. ¿Por qué obtengo resultados diferentes de compiladores diferentes o incluso del mismo compilador? Eso me hace pensar que el estándar de alguna manera no especifica el comportamiento en este caso.

Gracias por cualquier sugerencia, comentario e interpretación estándar.

Actualización
Me gustaría entender el comportamiento del compilador. Más precisamente, por qué no se generan errores si se infringe la ODR. Una hipótesis es que dado que todas las funciones en las clases UsedClass1 y UsedClass2 están marcadas como inline (y por lo tanto C++ 03 3.2 es no violado) el enlazador no informa errores, pero en este caso las salidas [EG1], [EG3], [EG4] parecen extrañas.

+7

Se infringe el ODR, y no se garantiza un error del vinculador cuando eso sucede. El comportamiento no está definido. –

+0

No puedo responder su pregunta en relación con los estándares, pero esta es la razón por la que tenemos espacios de nombres. – 111111

Respuesta

7

Su programa infringe la Regla de una sola definición e invoca un Comportamiento indefinido.
El estándar no exige un mensaje de diagnóstico si rompe el ODR, pero el comportamiento no está definido.

C++ 3.2 03 Una regla definición

Sin unidad de traducción deberá contener más de una definición de cualquier variable, función, tipo de clase, tipo de enumeración o plantilla. ...

Cada programa debe contener exactamente una definición de cada función u objeto no en línea que se utiliza en ese programa; no se requiere diagnóstico. La definición puede aparecer explícitamente en el programa, se puede encontrar en la biblioteca estándar o definida por el usuario, o (cuando corresponda) está implícitamente definida (ver 12.1, 12.4 y 12.8). Se debe definir una función en línea en cada unidad de traducción en la que se utiliza.

Además, el estándar define los requisitos específicos para la existencia de múltiples definiciones de un símbolo, que se definen acertadamente en el Pár. # 5 de 3.2.

No puede haber más de una definición de un tipo de clase (cláusula 9), tipo de enumeración (7.2), función en línea con enlace externo (7.1.2), plantilla de clase (cláusula 14) función, no estático plantilla (14.5.5), miembro de datos estáticos de una plantilla de clase (14.5.1.3), función miembro de una plantilla de clase (14.5.1.1) o especialización de plantilla para la que no se especifican algunos parámetros de plantilla (14.7, 14.5.4) en un programa siempre que cada definición aparezca en una unidad de traducción diferente, y siempre que las definiciones satisfagan los siguientes requisitos. Dado que una entidad llamada D se definió en más de una unidad de traducción, entonces

- cada definición de D constará de la misma secuencia de tokens; y ...

+0

Falta la regla que aplica, que sería la que se refiere a las definiciones inconsistentes de una función en línea. –

+1

La parte específica de/5 que es relevante aquí, es que "cada definición de D consistirá en la misma secuencia de tokens". Las dos definiciones de 'UsedClass' difieren en 1 token (una cadena literal), que es suficiente. –

+0

'Se debe definir una función en línea en cada unidad de traducción en la que se utiliza. Estoy usando solo funciones en línea en el ejemplo, que están definidas en cada unidad de traducción. – user1221434

4

Por qué son salidas [EG1], [EG3], [EG4] posible?

La respuesta simple es que el comportamiento no está definido, por lo que todo es posible.

La mayoría de los compiladores manejan una función en línea al generar una copia en cada unidad de traducción en la que está definida; el vinculador entonces elige arbitrariamente uno para incluir en el programa final. Por eso, con las optimizaciones desactivadas, llama a la misma función en ambos casos. Con las optimizaciones habilitadas, la función puede ser inlineada por el compilador, en cuyo caso cada llamada incorporada usará la versión definida en la unidad de traducción actual.

Eso me hace pensar que el estándar de alguna manera no especifica el comportamiento en este caso.

Correcto. Romper la regla de una definición proporciona un comportamiento indefinido y no se requiere diagnóstico.

+0

Suena bastante razonable. Por ejemplo, '__attribute__ ((always_inline))' en gcc hace que la salida del caso EG1 sea igual que EG2. Gracias por confirmar mi suposición. – user1221434

12

Esta es la regla que prohíbe lo que está haciendo (la redacción de C++ 11), de la sección 3.2 de la Norma:

No puede haber más de una definición de un tipo de clase (cláusula 9), tipo de enumeración (7.2), función en línea con enlace externo (7.1.2), plantilla de clase (Cláusula 14), plantilla de función no estática (14.5.6), miembro de datos estáticos de una plantilla de clase (14.5.1.3), función de miembro de una plantilla de clase (14.5.1.1), o especialización de plantilla para la cual algunos parámetros de plantilla son no especi fi cado (14.7, 14.5.5) en un programa, siempre que cada definición aparezca en una unidad de traducción diferente, y siempre que las definiciones cumplan con los siguientes requisitos. Dada una entidad tal llamado D se define en más de una unidad de traducción, a continuación,

  • cada definición de D consistirá en la misma secuencia de tokens; y

  • en cada definición de D, nombres correspondientes, miraron según 3.4, se referirá a una entidad definida dentro de la definición de D, o se referirá a la misma entidad, después de la resolución de sobrecarga (13.3) y después de emparejar de la especialización de plantilla parcial (14.8.3), excepto que un nombre puede referirse a un objeto const con enlace interno o sin vinculación si el objeto tiene el mismo tipo literal en todas las definiciones de D, y el objeto se inicializa con una expresión constante (5.19), y se usa el valor (pero no la dirección) del objeto, y el objeto tiene el mismo valor en todas las definiciones de D; y

  • en cada definición de D, las entidades correspondientes deben tener el mismo enlace de idioma; y

  • , los operadores sobrecargados a los que se hace referencia, las llamadas implícitas a funciones de conversión, constructores, nuevas funciones de operador y funciones de borrado de operador, se refieren a la misma función oa una función definida dentro de la definición de D; y

  • en cada definición de D, un argumento predeterminado utilizado por una llamada de función (implícito o explícito) se trata como si su secuencia de tokens estaban presentes en la definición de D; es decir, el argumento predeterminado está sujeto a los tres requisitos descritos anteriormente (y, si el argumento predeterminado tiene subexpresiones con argumentos predeterminados, este requisito se aplica recursivamente).

  • si D es una clase con un constructor declarado implícitamente-(12.1), es como si el constructor se define implícitamente en cada unidad de traducción, donde se utiliza ODR-y lo implícito de fi nición en cada unidad de traducción se llame el mismo constructor para una clase base o un miembro de clase de D.

En su programa, usted está violando el ODR para class UsedClass debido a que las fichas son diferentes en diferentes unidades de compilación. Podría solucionarlo moviendo la definición de UsedClass::doit() fuera del cuerpo de la clase, pero la misma regla se aplica al cuerpo de las funciones en línea.

Cuestiones relacionadas