C ++: разные классы с одинаковыми именами в разных единицах перевода - PullRequest
7 голосов
/ 20 февраля 2012

Рассмотрим следующий пример:

// 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();
}

Код компилируется без ошибок компилятора или компоновщика. Но вывод для меня странный:

  • gcc (Red Hat 4.6.1-9) в Fedora x86_64 без оптимизации [ EG1 ]:

    Используется Класс 1 (0x7fff0be4a6ff) до попадания
    ИспользуетсяКласс 1 (0x7fff0be4a72e) doit hit

  • аналогично [EG1], но с включенной опцией -O2 [ EG2 ]:

    Используется Класс 2 (0x7fffcef79fcf) до попадания
    Бывший класс 1 (0x7fffcef79fff) доит

  • msvc2005 (14.00.50727.762) в Windows XP 32bit без оптимизации [ EG3 ]:

    Используется Класс 1 (0012FF5B) до попадания
    Б / уКласс 1 (0012FF67) доит

  • аналогично [EG3], но с включенным / O2 (или / Ox) [ EG4 ]:

    Используется Класс 1 (0012FF73) до попадания
    Б / уКласс 1 (0012FF7F) доит

Я ожидал бы либо ошибку компоновщика (при условии, что нарушено правило ODR), либо вывод, как в [EG2] (код встроен, из единицы преобразования ничего не экспортируется, правило ODR сохраняется). Итак мои вопросы:

  1. Почему возможны выходы [EG1], [EG3], [EG4]?
  2. Почему я получаю разные результаты от разных компиляторов или даже от одного и того же компилятора? Это заставляет меня думать, что стандарт как-то не определяет поведение в этом случае.

Спасибо за любые предложения, комментарии и стандартные интерпретации.

Update
Я хотел бы понять поведение компилятора. Точнее, почему нет ошибок, генерируемых в случае нарушения ODR. Гипотеза состоит в том, что, поскольку все функции в классах UsedClass1 и UsedClass2 помечены как встроенные (и, следовательно, C ++ 03 3.2 не нарушена), компоновщик не ' • сообщать об ошибках, но в этом случае выходные данные [EG1], [EG3], [EG4] кажутся странными.

Ответы [ 3 ]

13 голосов
/ 20 февраля 2012

Это правило, которое запрещает то, что вы делаете (формулировка C ++ 11), из раздела 3.2 Стандарта:

Может быть более одного определениятип класса (раздел 9), тип перечисления (7.2), встроенная функция с внешней связью (7.1.2), шаблон класса (раздел 14), шаблон нестатической функции (14.5.6)), член статических данных шаблона класса (14.5.1.3), функция-член шаблона класса (14.5.1.1) или специализация шаблона, для которой некоторые параметры шаблона не определены (14.7, 14.5.5) вПрограмма при условии, что каждое определение появляется в другой единице перевода, и при условии, что определения удовлетворяют следующим требованиям.Если такой объект с именем D определен более чем в одной единице перевода, то

  • каждое определение D должно состоять из одной и той же последовательности токенов

  • в каждом определении D, соответствующие имена, просмотренные в соответствии с 3.4, должны относиться к объекту, определенному в определении D, или должны ссылаться на то же самоеобъект, после разрешения перегрузки (13.3) и после сопоставления частичной специализации шаблона (14.8.3), за исключением того, что имя может ссылаться на объект const с внутренней связью или без нее, если объект имеет одинаковый литеральный тип во всех определенияхD, и объект инициализируется постоянным выражением (5.19), и используется значение (но не адрес) объекта, и объект имеет одинаковое значение во всех определениях D

  • в каждом определении D, соответствующие объекты должны иметь одинаковые языковые связи;и

  • в каждом определении D упомянутые перегруженные операторы, неявные вызовы функций преобразования, конструкторы, новые операторные функции и функции удаления операторов должны ссылаться на одну и ту же функциюили к функции, определенной в определении D;и

  • в каждом определении D аргумент по умолчанию, используемый (неявным или явным) вызовом функции, обрабатывается так, как если бы его последовательность токенов присутствовала в определении D;то есть аргумент по умолчанию подчиняется трем требованиям, описанным выше (и, если аргумент по умолчанию имеет подвыражения с аргументами по умолчанию, это требование применяется рекурсивно).

  • , если D - это класс с неявно объявленным конструктором (12.1), как если бы конструктор был неявно определен в каждой единице перевода, где он используется odr, и неявное определение в каждой единице перевода должно вызывать один и тот же конструктор для базыкласс или член класса D.

В вашей программе вы нарушаете ODR для class UsedClass, поскольку токены различаются в разных единицах компиляции.Это можно исправить, переместив определение UsedClass::doit() за пределы тела класса, но то же правило применимо к телу встроенных функций.

7 голосов
/ 20 февраля 2012

Ваша программа нарушает правило единого определения и вызывает неопределенное поведение.
Стандарт не предписывает диагностическое сообщение, если вы прерываете ODR, но его поведение не определено.

C ++ 03 3.2 Одно правило определения

Ни одна единица перевода не должна содержать более одного определения любой переменной, функции, типа класса, типа перечисления или шаблона. ...

Каждая программа должна содержать ровно одно определение каждой не встроенной функции или объекта, которая используется в этой программе; Диагностика не требуется. Определение может явным образом появиться в программе, оно может быть найдено в стандартной или пользовательской библиотеке или (при необходимости) неявно определено (см. 12.1, 12.4 и 12.8). Встроенная функция должна быть определена в каждой единице перевода, в которой она используется.

Кроме того, стандарт определяет особые требования к существованию нескольких определений символа, которые точно определены в параграфе 5 из 3.2.

Может быть несколько определений типа класса (раздел 9), типа перечисления (7.2), встроенной функции с внешней связью (7.1.2), шаблона класса (раздел 14), шаблона нестатической функции (14.5). .5), статический член данных шаблона класса (14.5.1.3), функция-член шаблона класса (14.5.1.1) или специализация шаблона, для которого некоторые параметры шаблона не указаны (14.7, 14.5.4) в программе при условии, что каждое определение появляется в отдельной единице перевода, и при условии, что определения удовлетворяют следующим требованиям. Если такая сущность с именем D определена более чем в одной единице перевода, то

- каждое определение D должно состоять из одинаковой последовательности токенов; и ...

4 голосов
/ 20 февраля 2012

Почему возможны выходы [EG1], [EG3], [EG4]?

Простой ответ заключается в том, что поведение не определено, поэтому все возможно.

Большинство компиляторов обрабатывают встроенную функцию, генерируя копию в каждой единице перевода, в которой она определена; Затем компоновщик произвольно выбирает один из них для включения в конечную программу. Вот почему при отключенных оптимизациях она вызывает одну и ту же функцию в обоих случаях. При включенных оптимизациях функция может быть встроена компилятором, и в этом случае каждый встроенный вызов будет использовать версию, определенную в текущей единице перевода.

Это заставляет меня думать, что стандарт как-то не определяет поведение в этом случае.

Это верно. Нарушение правила одного определения приводит к неопределенному поведению, и диагностика не требуется.

...