Ответ 1
В самом деле, существует одно правило определения, указывающее, что встроенная функция должна быть определена в каждой используемой единицы перевода. Далее перечислены детали Gory. Первый 3.2/3
:
Каждая программа должна содержать ровно одно определение каждой не-встроенной функции или объекта, которая используется в этой программе; не требуется диагностика. Определение может явно отображаться в программе, оно может быть найдено в стандартной или определяемой пользователем библиотеке или (если необходимо), оно неявно определено (см. 12.1, 12.4 и 12.8). Встроенная функция должна быть определена в каждой единицы перевода, в которой она используется.
И, конечно, 7.1.2/4
:
Встроенная функция должна быть определена в каждой единицы перевода, в которой она используется, и должна иметь точно такое же определение в каждом случае (3.2). [Примечание: вызов встроенной функции может быть встречен до того, как ее определение появится в блоке перевода. ] Если функция с внешней связью объявлена встроенной в одну единицу перевода, она должна быть объявлена встроенной во все единицы перевода, в которых она отображается; диагностика не требуется. Встроенная функция с внешней связью должна иметь один и тот же адрес во всех единицах перевода. Статическая локальная переменная во внешней встроенной функции всегда относится к одному и тому же объекту. Строковый литерал во внешней встроенной функции - это тот же объект в разных единицах перевода.
Однако, если вы определяете свою функцию в определении класса, она неявно объявляется как функция inline
. Это позволит вам включить определение класса, содержащее это тело встроенной функции, несколько раз в вашей программе. Так как функция имеет связь external
, любое ее определение будет относиться к той же функции (или более gory - к тому же entity
).
Горы подробности о моей претензии. Первый 3.5/5
:
Кроме того, функция-член, член статических данных, класс или перечисление области видимости класса имеет внешнюю связь, если имя класса имеет внешнюю привязку.
Тогда 3.5/4
:
Имя, имеющее область пространства имен, имеет внешнюю связь, если это имя [...] именованного класса (раздел 9) или неназванный класс, определенный в объявлении typedef, в котором класс имеет имя typedef для целей привязки.
Это "имя для целей связи" - это забавная вещь:
typedef struct { [...] } the_name;
Поскольку теперь у вас есть несколько определений одного и того же объекта в ваших программах, другое дело ODR, как правило, ограничивает вас. 3.2/5
следует за скучным материалом.
В программе может быть несколько определений типа класса (раздел 9), тип перечисления (7.2), встроенная функция с внешней связью (7.1.2) [...] в программе, при условии, что каждое определение появляется в другой единицы перевода, и при условии, что определения удовлетворяют следующим требованиям. Учитывая такой объект с именем D, определенный более чем в одной единицы перевода, тогда
- каждое определение D должно состоять из одной и той же последовательности токенов; и
- в каждом определении D соответствующие имена, просмотренные в соответствии с 3.4, относятся к сущности, определенной в определении D, или должны ссылаться на один и тот же объект после разрешения перегрузки (13.3) и после согласования частичного шаблона специализация (14.8.3) [...]
Теперь я отрезал некоторые неважные вещи. Вышеупомянутые два важных момента, чтобы помнить о встроенных функциях. Если вы определяете внешнюю встроенную функцию несколько раз, но определяете ее по-разному, или если вы ее определяете, а имена, используемые в ней, разрешают разные сущности, то вы выполняете поведение undefined.
Правило, согласно которому функция должна быть определена в каждом TU, в котором она используется, легко запомнить. И это то же самое легко запомнить. Но как насчет разрешения имени? Вот пример. Рассмотрим статическую функцию assert_it
:
static void assert_it() { [...] }
Теперь, поскольку static
даст ему внутреннюю связь, когда вы включите его в несколько единиц перевода, тогда каждое определение будет определять другой объект. Это означает, что вам не разрешено использовать assert_it
из встроенной функции extern, которая будет определена несколько раз в программе: поскольку происходит то, что встроенная функция будет ссылаться на один объект с именем assert_it
в одном TU, но к другому объекту с тем же именем в другом ТУ. Вы обнаружите, что все это скучная теория, и компиляторы, вероятно, не будут жаловаться, но я нашел этот пример, в частности, показывает связь между ODR и сущностями.
Далее следует вернуться к вашей конкретной проблеме.
Ниже приведены те же самые вещи:
struct A { void f() { } };
struct A { inline void f(); }; void A::f() { } // same TU!
Но это другое, поскольку функция не является встроенной. Вы нарушите ODR, поскольку у вас есть несколько определений f
, если вы включаете заголовок более одного раза
struct A { void f(); }; void A::f() { } // evil!
Теперь, если вы помещаете inline
в объявление f
внутри класса, но затем опускаете его определение в заголовке, тогда вы нарушаете 3.2/3
(и 7.1.2/4
, который говорит то же самое, только более подробно), так как функция не определена в этой единицы перевода!
Обратите внимание, что в C (C99) inline имеет другую семантику, чем в С++. Если вы создаете внешнюю встроенную функцию, сначала прочитайте хорошую бумагу (желательно стандарт), так как это действительно сложно в C (в принципе, для любого используемого встроенного определения функции потребуется другое определение не встроенной функции в другом Статические встроенные функции TU в C легко обрабатываются. Они ведут себя как любая другая функция, кроме обычного подсказки "встроенной подстановки". Static inline
как в C, так и в С++ служит только как подсказка inline-substitution. уже создаст другую сущность в любое время (из-за внутренней привязки), inline
просто добавит подсказку inline-substitution - не более.