Имеют ли классы внешнюю связь?

У меня есть 2 файла A.cpp и B.cpp которые выглядят примерно как

A.cpp
----------
class w
{
public:
    w();
};


B.cpp
-----------
class w
{
public:
    w();
};

теперь я где-то читал (http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fcplr082.htm) что классы имеют внешнюю связь. Поэтому при построении я ожидал ошибки множественного определения, но, наоборот, это сработало как шарм. Однако, когда я определил класс w в A.cpp я получил ошибку переопределения, которая заставляет меня верить. что классы имеют внутреннюю связь.

Я что-то пропустил?

5 ответов


внешняя связь означает символ (функция или глобальная переменная) доступен во всей программе и внутренняя перелинковка означает, что он доступен только в одной единице перевода. связь символа явно контролируется с помощью ключевых слов extern и static, а связь по умолчанию-extern для неконстентных символов и static (internal) для символов const.

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

программа фактически нарушает Одно Правило Определения но компилятору трудно обнаружить ошибку, потому что они находятся в разных единицах компиляции. И даже компоновщик, похоже, не может обнаружить его как ошибку.

C++ позволяет обойти чтобы обойти правило одного определения, используя пространство имен.

[UPDATE] Из Стандарта C++03
§ 3.2 одно правило определения, раздел 5 гласит:

может быть несколько определений типа класса... в программе при условии, что каждое определение отображается в другой единице перевода, и при условии, что определения удовлетворяют следующим требованиям. С учетом такая сущность с именем D определяется в нескольких единицах перевода, тогда каждое определение D должно состоять из одной и той же последовательности токенов.


правильный ответ-да, имя класса может иметь внешние связи. Предыдущие ответы неверны и вводят в заблуждение. Код, который вы показываете, является законным и распространенным.

имя класса В C++03 может иметь внешнюю связь или не иметь никакой связи. В C++11 имя класса может дополнительно иметь внутреннюю связь.

C++03

§3.5 [basic.link]

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

имена классов могут иметь внешние связи.

имя, имеющее область пространства имен, имеет внешнюю связь, если это имя из

[...]

- именованный класс (предложение 9) или безымянный класс, определенный в объявлении typedef, в котором класс имеет имя typedef для связи цели (7.1.3)

имена классов не могут иметь связи.

имена, не охваченные этими правилами, не имеют связи. Кроме того, кроме как отмечено, что имя, объявленное в локальной области (3.3.2), не имеет связи. Имя без привязки (в частности, объявлено имя класса или перечисления в локальной области (3.3.2)) не должны использоваться для объявления лица с связь.

В C++11 первые изменения цитаты и имена классов в области пространства имен теперь могут имейте внешнюю или внутреннюю связь.

неименованное пространство имен и пространство имен, объявленное прямо или косвенно внутри безымянного пространства имен есть внутренняя связь. Все другие пространства имен имейте внешнюю связь. Имя, имеющее область пространства имен, которая не была учитывая внутреннюю связь выше [имена классов не были] имеет ту же связь как заключающее пространство имен, если это имя

[...]

- именованный класс (пункт 9) или неназванный класс определено в typedef объявление, в котором класс имеет имя typedef для связи назначения (7.1.3);

вторая цитата также изменяется, но вывод тот же, имена классов могут не иметь связи.

имена, не охваченные этими правилами, не имеют связи. Кроме того, кроме как отмечено, что имя, объявленное в области блока (3.3.3), не имеет связи. Тип говорят, что связь есть тогда и только тогда, когда:

- это класс или тип перечисления что называется (или имя целей связи (7.1.3)) и название имеет связь; или

- это неназванный класс или член перечисления класса со связью;

некоторые из ответов здесь объединяют абстрактное понятие связи в стандарте C++ с компьютерной программой, известной как компоновщик. Стандарт C++ не придает особого значения символу слова. Символ-это то, что компоновщик разрешает при объединении объектных файлов в исполняемый файл. Формально это не имеет отношения к понятию связи в стандарте C++. Документ всегда обращается только к компоновщикам в сноске относительно кодировки символов.

наконец, ваш пример-legal C++ и не является нарушением ODR. Рассмотреть следующее.

C.h
----------
class w
{
public:
    w();
};


A.cpp
-----------
#include "C.h"


B.cpp
-----------
#include "C.h"

возможно, это выглядит знакомо. После оценки директив препроцессора мы остаемся с исходным примером. Ссылка Википедии, предоставленная Alok Save, даже утверждает это как исключение.

некоторые вещи, такие как типы, шаблоны и внешние встроенные функции, могут определяется в нескольких единицах перевода. Для данного объекта каждый определение должно быть одинаковым.

правило ODR учитывает содержимое. То, что вы показываете, фактически требуется для того, чтобы единица перевода использовала класс как полный тип.

§3.5 [basic.защита.odr]

требуется ровно одно определение класса в переводе если класс используется таким образом, который требует, чтобы тип класса был полный.

edit-вторая половина ответа Джеймса Канзе получила это право.


технически, как указывает Максим, связь применяется к символам, а не к сущности, которые они обозначают. Но связь символа частично определяется тем, что он обозначает: символы, которые называют классы, определенные в область пространства имен имеет внешнюю связь и w обозначает тот же объект в обоих A.cpp и B.cpp.

C++ имеет два разных набора правил, касающихся определения сущности: некоторые сущности, такие как функции или переменные, могут быть только определенный раз в целом программа. Определяя их еще не раз результат в неопределенном поведении; большинство реализаций будет (большинство время, во всяком случае) дайте ошибку множественного определения, но это не требуется или гарантированно. Другие сущности, такие как классы или шаблоны требуется определить в каждой единице перевода, которая их использует, с дополнительное требование о том, чтобы каждое определение было идентичным: одинаковым последовательность токенов и все символы, привязанные к одной сущности, с очень ограниченное исключение для символов в постоянные выражения, при условии, что адрес никогда не берется. Нарушение этих требований также не определено поведение, но в этом случае, большинство систем даже не предупреждают.


классы не имеют связи, чтобы быть педантичными.

связь применяется только к symbols, то есть функции и переменные, или код и данные.


класс декларация

class w
{
public:
    w();
};

не производит никакого кода или символов, поэтому нет ничего, что могло бы быть связано и иметь "связь". Однако, когда ваш конструктор w () является определена ...

w::w()
{
  // object initialization goes here
}

он будет иметь внешних связей. Если вы определяете его в обоих A.cpp и B.cpp, будет столкновение имен; что произойдет тогда, зависит от вашего компоновщика. Компоновщики MSVC, например, завершатся с ошибкой lnk2005 "функция уже определена" и / или LNK1169 "найден один или несколько умноженных определенных символов". Компоновщик GNU g++ будет вести себя аналогично. (Для дубликата шаблон методы, они вместо этого устранят все, кроме одного экземпляра; документы GCC называют это"Борланд модель").

есть четыре способа решить эту проблему:

  1. если оба класса идентичны, поместите определения только в один .файл cpp.
  2. Если вам нужны два разных, внешне связанных реализации class w, положил их в разные пространства имен.
  3. избегайте внешней связи, помещая определения в анонимные пространства имен.
namespace
{
  w::w()
  {
    // object initialization goes here
  }
}

Everying в анонимном пространстве имен имеет внутреннюю связь, поэтому вы также можете использовать его в качестве замены для static объявления (которые невозможны для методов класса).

  1. избегайте создания символов путем определения методов inline:
inline w::w()
{
  // object initialization goes here
}

No 4 будет работать, только если ваш класс не имеет статических полей (переменных класса), и он будет дублировать код встроенных методов для каждого вызова функции.