Должен ли я защищать от нарушений ODR при объявлении шаблонов функций в исходных файлах?

#c #templates #one-definition-rule

#c #шаблоны #одно определение-правило

Вопрос:

«Обычная» функция, когда она определена и используется исключительно в одной единице перевода, объявляется и определяется следующим образом:

 // implementation.cpp
static void fun(int arg) { /* implementation */ }
 

в качестве альтернативы вы можете удалить static ключевое слово и поместить функцию в неназванное пространство имен. Но невыполнение одного из вышеперечисленных действий может привести к нарушениям ODR: если у другой единицы перевода также есть декларация / определение fun (с теми же аргументами), лайкеру, возможно, придется молча отказаться от одной из реализаций, чтобы сохранить одно определение (в этом случае вам лучше иметь точно такое же определениепрописано для обоих, или вы увидите неожиданное поведение).

Как описано выше, проблема в большей степени «ориентирована на реализацию», но даже стандарт настоятельно рекомендует вам защищать от таких побочных эффектов с помощью static or namespace { /**/ } .

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

 template <class T>
/* static ? */ void fun(T arg) { /*...*/ }
 

В cppreference упоминается, что если функция — inline это нормально, очевидно (требования к единому определению предъявляются только для нестроевых функций), поэтому никаких проблем с ODR там нет:

Одно и только одно определение каждой нестроевой функции или переменной, используемой odr (см. Ниже), должно отображаться во всей программе

Мы знаем, что шаблоны функций встроены, но достаточно ли этого, чтобы гарантировать правильность ODR?

Комментарии:

1. Ваша цитата — это единственное правило определения для нестроевых функций и переменных. Прочитайте следующий абзац для требований к встроенным функциям. (Нет, определение не исчезает. inline Ключевое слово не имеет никакого отношения к непосредственному использованию содержимого функции, также известному как встраивание функции.)

2. @JaMiT Итак, я должен использовать static или поместить его в неназванное пространство имен? (Я отредактирую текст перед цитатой, чтобы лучше соединить точки между встроенными и не)

3. У вас может быть столько экземпляров шаблонов, сколько вы хотите. Компоновщик примет один, и все в порядке. Обычно мы создаем несколько экземпляров в разных единицах компиляции. В таком случае ничего особенного не нужно делать.

4. @Klaus Очевидно, что проблема возникнет, когда другой автор попытается объявить fun что-то другое в другой единице перевода. Это то, что происходит и в случае, не являющемся шаблоном (если у нас простой случай с одним main.cpp , не многие люди используют неназванные пространства имен). Я не спрашиваю о нескольких экземплярах, но какова наилучшая практика, должен ли я предварительно объявить его как статический? Если другая единица перевода содержит `template <class T> fun(T arg) { /* другая реализация здесь */ }, будет ли проблема (n ODR)?

5. Хорошо, вы хотите сказать: «Кто-то другой» создаст шаблонную функцию с той же сигнатурой в другом файле и будет использовать ее из разных модулей компиляции. ОК. Но если вы опасаетесь таких вещей, вам также следует опасаться, что кто-то другой также создаст пространство имен с другим содержимым и добавит другие материалы, которые нарушают ODR. Извините, но это похоже не на техническую проблему, а на что-то связанное с управлением проектами, обзорами и документацией. Если такое может произойти, как вы определяете, например, заказы на включение? Только мои два цента

Ответ №1:

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

каждое определение состоит из одной и той же последовательности токенов

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

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

Примечание: Пометка функции static или помещение ее в анонимное пространство имен приведет к тому, что у нее больше не будет внешней связи.


Я хотел бы отметить, что в этом вопросе слово «очевидно» используется довольно распространенным, но нестандартным способом, означающим «Мне нужно, чтобы следующее утверждение false было истинным, поэтому примите его без вопросов». Может быть полезно научиться останавливаться и задавать себе вопросы всякий раз, когда вы чувствуете необходимость обосновать что-то как «очевидное».

Комментарии:

1. Вопрос не имеет ничего общего с отсутствием определения и все, что связано с тем, что определение видно / доступно более одного раза. Я не беспокоюсь о том, что мой модуль перевода не сможет найти функцию, объявленную внутри него; Я беспокоюсь, что будущая разработка может нарушить мою программу, потому что другой автор решит объявить другой шаблон функции, который имеет то же имя, и никто из нас не потрудился использовать static или unnamed namespace .

2. «Я не беспокоюсь, что мой модуль перевода не сможет найти функцию, объявленную внутри него» — вы должны быть. Если другой автор решит объявить другой шаблон функции, который имеет то же имя, ваш модуль перевода может в конечном итоге вызвать шаблон другого автора.

3. cppreference говорит, что наличие функции fun , объявленной и определенной внутри source1.cpp и source2.cpp (с другой реализацией), не является нарушением ODR, когда fun является встроенным, и накладывает ограничение на «ровно одно» определение ТОЛЬКО для нестроевых функций. Почему? Мои предположения связаны с тем, что, когда функция является встроенной, вы не можете найти ее символ в объектном файле, поскольку его содержимое используется напрямую, поэтому компоновщик не боится отбрасывать одно из определений

4. @LorahAttkins Дважды проверьте страницу, которую вы читаете. Это могут быть правила для C, а не правила для C .

5. Я ценю ваши усилия по изучению этого, но я спрашиваю совершенно о другом. Является ли что-то, что является обычной практикой для обычных функций (объявление их как статических в файлах cpp), необходимым для шаблонов функций или нет? вот и все…