Разве встроенный C ++ не является полностью необязательным?
Вопрос
У меня есть класс, в котором был встроенный элемент, но позже я решил, что хочу удалить реализацию из заголовков, поэтому я переместил тело элементов функций в cpp-файл.Сначала я просто оставил встроенную подпись в заголовочном файле (неаккуратный я), и программе не удалось правильно связать.Затем я исправил свой заголовок, и, конечно, все это работает нормально.
Но разве inline не был полностью необязательным?
В коде:
Первый:
//Class.h
class MyClass
{
void inline foo()
{}
};
Далее изменено на (не будет ссылки):
//Class.h
class MyClass
{
void inline foo();
};
//Class.cpp
void MyClass::foo()
{}
И затем (будет работать нормально):
//Class.h
class MyClass
{
void foo();
};
//Class.cpp
void MyClass::foo()
{}
Я думал, что встроенная функция необязательна, и предполагал, что смогу обойтись предупреждением за свою неаккуратность, но не ожидал ошибки привязки.Что является правильной / стандартной вещью, которую компилятор должен сделать в этом случае, заслужил ли я свою ошибку в соответствии со стандартом?
Решение
Действительно, существует одно правило определения, гласящее, что встроенная функция должен быть определенным в каждой используемой единице перевода.Далее следуют кровавые подробности.Первый 3.2/3
:
Каждая программа должна содержать ровно одно определение каждой нестроевой функции или объекта, который используется в этой программе;диагностика не требуется.Определение может появиться явно в программе, его можно найти в стандартной или определяемой пользователем библиотеке, или (при необходимости) оно определено неявно (см. 12.1, 12.4 и 12.8).Встроенная функция должна быть определена в каждой единице перевода, в которой она используется.
И, конечно же 7.1.2/4
:
Встроенная функция должна быть определена в каждой единице перевода, в которой она используется, и должна иметь точно такое же определение в каждом конкретном случае (3.2).[Примечание:вызов встроенной функции может быть обнаружен до того, как ее определение появится в модуле перевода.] Если функция с внешней связью объявлена встроенной в одной единице перевода, она должна быть объявлена встроенной во всех единицах перевода, в которых она появляется;диагностика не требуется.Встроенная функция с внешней связью должна иметь один и тот же адрес во всех единицах преобразования.Статическая локальная переменная во внешней встроенной функции всегда ссылается на один и тот же объект.Строковый литерал во внешней встроенной функции - это один и тот же объект в разных единицах преобразования.
Однако, если вы определяете свою функцию в определении класса, она неявно объявляется как inline
функция.Это позволит вам включать определение класса, содержащее это тело встроенной функции, несколько раз в вашу программу.Поскольку функция имеет external
связь, любое ее определение будет относиться к то же самое функция (или более кровавая - к тому же 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) [...]
Сейчас я отсекаю кое-какие несущественные вещи.Вышесказанное - это два важных аспекта, которые следует помнить о встроенных функциях.Если вы определяете внешнюю встроенную функцию несколько раз, но определяете ее по-разному, или если вы определяете ее и имена, используемые в ней, разрешаются для разных сущностей, то вы выполняете неопределенное поведение.
Правило, согласно которому функция должна быть определена в каждом техническом задании, в котором она используется, легко запомнить.И то, что это одно и то же, тоже легко запомнить.Но как насчет этой штуковины с разрешением имен?Вот несколько примеров.Рассмотрим статическую функцию assert_it
:
static void assert_it() { [...] }
Теперь, с тех пор как static
даст ему внутреннюю связь, когда вы включите его в несколько единиц перевода, тогда каждое определение будет определять другая сущность.Это означает, что вы нет разрешено использовать assert_it
из внешней встроенной функции, которая будет определена в программе несколько раз:Потому что происходит то, что встроенная функция будет ссылаться на один вызываемый объект assert_it
в одном TU, но к другому объекту с тем же именем в другом 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 ++.Если вы создаете внешнюю встроенную функцию, вам следует сначала прочитать какую-нибудь хорошую статью (предпочтительно стандартную), поскольку они действительно сложны в C (в принципе, для любого используемого встроенного определения функции потребуется другое, не встроенное определение функции в другом TU.статические встроенные функции в C просты в обращении.Они ведут себя как любая другая функция, за исключением обычной подсказки "встроенная подстановка".статический inline
как в C, так и в C ++ служит только как подсказка для встроенной замены.Поскольку static уже создает другую сущность каждый раз, когда она используется (из-за внутренней связи), inline
просто добавлю подсказку о встроенной замене - не более.
Другие советы
Независимо от того, на самом деле на самом деле вставлен метод, по собственному усмотрению компилятора. Однако наличие встроенного ключевого слова также повлияет на связь метода.
C ++ Связанность не является моей специальностью, поэтому я буду отдать должное ссылкам для лучшего объяснения.
- http://publib.boulder.ibm.com/infocenter/zos/v1r9/index.jsp?topic=/com.ibm.zos.r9.cbclx01/inline_linkage.htm
- http://en.wikipedia.org/wiki/inline_function
С другой стороны, вы можете просто подождать лит предоставить кровавые детали через час или около того;)
Пометьте: когда метод объявлен в линии, его определение должно быть вместе с его объявлением.
Что касается ответа harshath.jr, метод не должен быть объявлен в линии, если его определение имеет ключевое слово «встроенное», и это определение доступно в том же заголовке, т.е.:
class foo
{
void bar();
};
inline void foo::bar()
{
...
}
Это полезно для условного внедрения метода в зависимости от того, является ли сборка "отлаживать" или же "выпускать" вот так:
// Header - foo.h
class foo
{
void bar(); // Conditionally inlined.
};
#ifndef FOO_DEBUG
# include "foo.inl"
#endif
Файл «встроенный» может выглядеть так:
// Inline Functions/Methods - foo.inl
#ifndef FOO_DEBUG
# define FOO_INLINE inline
#else
# define FOO_INLINE
#endif
FOO_INLINE void foo::bar()
{
...
}
и реализации может понравиться следующее:
// Implementation file - foo.cpp
#ifdef FOO_DEBUG
# include "foo.inl"
#endif
...
Это не совсем красиво, но у него есть его использование, когда агрессивный inline становится головной болью отладки.