Разве встроенный C ++ не является полностью необязательным?

StackOverflow https://stackoverflow.com/questions/908830

  •  05-09-2019
  •  | 
  •  

Вопрос

У меня есть класс, в котором был встроенный элемент, но позже я решил, что хочу удалить реализацию из заголовков, поэтому я переместил тело элементов функций в 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/37.1.2/4 который говорит то же самое, только более подробно), поскольку функция не определена в этой единице перевода!

Обратите внимание, что в C (C99) семантика inline отличается от семантики в C ++.Если вы создаете внешнюю встроенную функцию, вам следует сначала прочитать какую-нибудь хорошую статью (предпочтительно стандартную), поскольку они действительно сложны в C (в принципе, для любого используемого встроенного определения функции потребуется другое, не встроенное определение функции в другом TU.статические встроенные функции в C просты в обращении.Они ведут себя как любая другая функция, за исключением обычной подсказки "встроенная подстановка".статический inline как в C, так и в C ++ служит только как подсказка для встроенной замены.Поскольку static уже создает другую сущность каждый раз, когда она используется (из-за внутренней связи), inline просто добавлю подсказку о встроенной замене - не более.

Другие советы

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

C ++ Связанность не является моей специальностью, поэтому я буду отдать должное ссылкам для лучшего объяснения.

С другой стороны, вы можете просто подождать лит предоставить кровавые детали через час или около того;)

Пометьте: когда метод объявлен в линии, его определение должно быть вместе с его объявлением.

Что касается ответа 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 становится головной болью отладки.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top