Есть ли у классов внешняя связь?
У меня есть 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=/com.ibm.xlcpp8a.doc/language/ref/cplr082.htm), что классы имеют внешние связь. Поэтому при сборке я ожидал многократную ошибку определения, но, наоборот, она работала как шарм. Однако когда я определил класс w в A.cpp, я получил ошибку переопределения, которая заставляет меня поверить, что классы имеют внутреннюю связь.
Я что-то здесь упускаю?
6 ответов
Внешняя связь означает, что символ (функция или глобальная переменная) доступен во всей вашей программе, а Внутренняя связь означает, что он доступен только в одной единице перевода. Вы явно управляете связыванием символа, используя ключевые слова extern и static, а связывание по умолчанию extern для неконстантных символов и static (internal) для константных символов.
Имя с внешней связью обозначает сущность, на которую можно ссылаться через имена, объявленные в той же области или в других областях той же единицы перевода (так же, как с внутренней связью), или дополнительно в других единицах перевода.
Программа фактически нарушает правило One Definition Rule, но компилятору трудно обнаружить ошибку, потому что они находятся в разных единицах компиляции. И даже компоновщик не может обнаружить это как ошибку.
C++ позволяет обходить обход правила One Definition Rule, используя пространство имен.
[ОБНОВЛЕНИЕ] Из C++ 03 Standard
§ 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++. Документ только когда-либо обращается к компоновщикам в сноске относительно кодировки символов.
Наконец, ваш пример является допустимым C++ и не является нарушением ODR. Учтите следующее.
C.h
----------
class w
{
public:
w();
};
A.cpp
-----------
#include "C.h"
B.cpp
-----------
#include "C.h"
Возможно, это выглядит знакомым. После того, как директивы препроцессора оценены, мы остаемся с оригинальным примером. Ссылка на Википедию, предоставленная Alok Save, даже утверждает это как исключение.
Некоторые вещи, такие как типы, шаблоны и внешние встроенные функции, могут быть определены в нескольких единицах перевода. Для данного объекта каждое определение должно быть одинаковым.
Правило ODR учитывает содержание. То, что вы показываете, на самом деле требуется для того, чтобы единица перевода использовала класс в качестве законченного типа.
§3.5 [basic.def.odr]
Точно одно определение класса требуется в единице перевода, если класс используется таким образом, что тип класса должен быть завершен.
редактировать - вторая половина ответа Джеймса Канзе понял это правильно.
Технически, как указывает Максим, связь применяется к символам, а не к сущностям, которые они обозначают. Но связь символа частично определяется тем, что он обозначает: символы, имена классов которых определены в области имен, имеют внешнюю связь, и w
обозначает один и тот же объект в обоих A.cpp
а также B.cpp
,
C++ имеет два разных набора правил, касающихся определения сущностей: некоторые сущности, такие как функции или переменные, могут быть определены только один раз во всей программе. Определение их более одного раза приведет к неопределенному поведению; большинство реализаций (в большинстве случаев, так или иначе) выдает ошибку множественного определения, но это не требуется и не гарантируется. Другие сущности, такие как классы или шаблоны, должны быть определены в каждой единице перевода, которая их использует, с дополнительным требованием, чтобы каждое определение было идентичным: та же последовательность токенов и все символы, привязанные к одной и той же сущности, с очень ограниченным исключение для символов в константных выражениях, при условии, что адрес никогда не берется. Нарушение этих требований также является неопределенным поведением, но в этом случае большинство систем даже не предупредит.
Объявление класса
class w
{
public:
w();
};
не производит никакого кода или символов, поэтому нет ничего, что могло бы быть связано и иметь "связь". Однако, когда ваш конструктор w() определен...
w::w()
{
// object initialization goes here
}
это будет иметь внешнюю связь. Если вы определите его как в A.cpp, так и в B.cpp, произойдет конфликт имен; что произойдет, зависит от вашего компоновщика. Линкеры MSVC, например, завершатся с ошибкой LNK2005 "функция уже определена" и / или LNK1169 "найден один или несколько кратно определенных символов". Линкер GNU g++ будет вести себя аналогично. (Для дублированных шаблонных методов вместо этого они будут исключать все, кроме одного экземпляра; документы GCC называют это "моделью Borland ").
Есть четыре способа решить эту проблему:
- Если оба класса идентичны, поместите определения только в один файл.cpp.
- Если вам нужны две разные, внешне связанные реализации
class w
поместите их в разные пространства имен. - Избегайте внешних связей, помещая определения в анонимное пространство имен.
namespace
{
w::w()
{
// object initialization goes here
}
}
Каждый в анонимном пространстве имен имеет внутреннюю связь, так что вы также можете использовать его как замену static
объявления (которые невозможны для методов класса).
- Избегайте создания символов, определяя встроенные методы:
inline w::w()
{
// object initialization goes here
}
№ 4 будет работать только в том случае, если в вашем классе нет статических полей (переменных класса), и он будет дублировать код встроенных методов для каждого вызова функции.
У классов нет никакой связи, чтобы быть педантичным.
Связь относится только к symbols
то есть функции и переменные или код и данные.
Поскольку вы не можете использовать класс, единственный способ дать статическую связь «классу» - это определить тип в анонимном пространстве имен. В противном случае он будет иметь внешнюю связь. Я помещаю класс в кавычки, потому что класс, который является типом, не имеет связи, вместо этого он ссылается на связь символов, определенных в области класса (но не на связь объекта, созданного с использованием класса). Сюда входят статические члены и методы, а также нестатические методы, но не нестатические члены, поскольку они являются лишь частью определения типа класса и дополнительно не объявляют/не определяют фактические символы.
«Класс», имеющий статическую компоновку, означает, что члены и методы, которые имели бы внешнюю компоновку или внешнюю компоновку comdat, теперь имеют только статическую компоновку — теперь они являются локальными символами, хотя эффект
Если вы объявите тип класса в анонимном пространстве имен, вы не сможете определить тип за пределами анонимного пространства имен, и он не будет скомпилирован. Вам нужно определить его в том же анонимном пространстве имен или другом анонимном пространстве имен в единице перевода (другое анонимное пространство имен не имеет значения, потому что все они объединены в одно и то же имя анонимного анонимного пространства имен).
Это единственный способ изменить привязку членов или методов класса/структуры, потому что он сделает его статическим членом и не изменит привязку,