Почему в заголовке указаны встроенные функции C ++?


120

NB. Это не вопрос о том, как использовать встроенные функции или как они работают, а скорее о том, почему они сделаны такими, какие есть.

Объявление функции-члена класса не требует определения функции, поскольку inlineэто только фактическая реализация функции. Например, в заголовочном файле:

struct foo{
    void bar(); // no need to define this as inline
}

Так почему же рядная реализация функциональных классов должна быть в заголовочном файле? Почему я не могу поместить в .cppфайл встроенную функцию ? Если бы я попытался поместить встроенное определение в .cppфайл, я бы получил сообщение об ошибке в следующих строках:

error LNK2019: unresolved external symbol 
"public: void __thiscall foo::bar(void)"
(?bar@foo@@QAEXXZ) referenced in function _main 
1>C:\Users\Me\Documents\Visual Studio 2012\Projects\inline\Debug\inline.exe 
: fatal error LNK1120: 1 unresolved externals



@Charles Я бы сказал, что вторая ссылка будет похожей, но я спрашиваю больше о логике, объясняющей, почему встроенный работает именно так.
thecoshman

2
В этом случае, я думаю, вы могли неправильно понять либо «встроенные», либо «заголовочные файлы»; ни одно из ваших утверждений не соответствует действительности. У вас может быть встроенная реализация функции-члена, и вы можете поместить определения встроенных функций в файл заголовка, просто это может быть не очень хорошей идеей. Вы можете уточнить свой вопрос?
CB Bailey,

Редактировать сообщение, я думаю, вы можете спросить о ситуациях, когда inlineпоявляется в определении, но не в предыдущем объявлении, и наоборот . Если да, то это может помочь: stackoverflow.com/questions/4924912/…
CB Bailey

Ответы:


122

Определение inlineфункции не обязательно должно быть в файле заголовка, но из-за единого правила определения ( ODR ) для встроенных функций идентичное определение функции должно существовать в каждой единице перевода, которая ее использует.

Самый простой способ добиться этого - поместить определение в файл заголовка.

Если вы хотите поместить определение функции в один исходный файл, вам не следует объявлять его inline. Отсутствие объявления функции не inlineозначает, что компилятор не может встроить функцию.

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


Но разве компилятор не компилирует файл .cpp, который включает файлы .h ... так что, когда он компилирует файл .cpp, он имеет как замедление, так и исходные файлы. Другие загруженные файлы заголовков принадлежат только им, поэтому компилятор может «доверять», что эти функции действительно существуют и будут реализованы в каком-то другом исходном файле
thecoshman

1
На самом деле это гораздо лучший ответ, чем мой, +1от меня!
SBI

2
@thecoshman: Есть два различия. Исходный файл против файла заголовка. По соглашению, заголовочный файл обычно относится к исходному файлу, который не является основой единицы перевода, а только # включен из других исходных файлов. Тогда есть объявление против определения. Вы можете иметь объявления или определения функций либо в файлах заголовков, либо в «обычных» исходных файлах. Боюсь, я не совсем понимаю, о чем вы спрашиваете в своем комментарии.
CB Bailey,

не волнуйтесь, я понимаю, почему это сейчас ... хотя я не уверен, кто на самом деле ответил на это. Комбинация вашего ответа и ответа @Xanatos объяснила мне это.
thecoshman

113

На это можно взглянуть двумя способами:

  1. Встроенные функции определены в заголовке, потому что для встраивания вызова функции компилятор должен иметь возможность видеть тело функции. Чтобы наивный компилятор мог это сделать, тело функции должно находиться в той же единице трансляции, что и вызов. (Современный компилятор может оптимизировать единицы трансляции, поэтому вызов функции может быть встроен, даже если определение функции находится в отдельной единице трансляции, но эти оптимизации дороги, не всегда включены и не всегда поддерживаются компилятор)

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

Два объяснения на самом деле сводятся к тому, что inlineключевое слово не совсем то, что вы ожидаете.

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

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


23

Это предел компилятора C ++. Если вы поместите функцию в заголовок, все файлы cpp, в которые она может быть встроена, смогут увидеть «источник» вашей функции, а встраивание может быть выполнено компилятором. В противном случае встраивание должно выполняться компоновщиком (каждый файл cpp компилируется в файл obj отдельно). Проблема в том, что сделать это в компоновщике будет намного сложнее. Аналогичная проблема существует с "шаблонными" классами / функциями. Они должны быть созданы компилятором, потому что у компоновщика возникнут проблемы с их экземпляром (созданием специальной версии). Некоторые более новые компиляторы / компоновщики могут выполнять "двухпроходную" компиляцию / компоновку, когда компилятор выполняет первый проход, затем компоновщик выполняет свою работу и вызывает компилятор для решения неразрешенных вещей (встроенные / шаблоны ...)


О, я вижу! да, это не для класса, который сам использует встроенную функцию, другой его код, который использует встроенные функции. Они видят только заголовочный файл для встраиваемого класса!
thecoshman

11
Я не согласен с этим ответом, это не ограничение компилятора C ++; это чисто то, как указаны языковые правила. Правила языка допускают простую модель компиляции, но не запрещают альтернативные реализации.
CB Bailey,

3
Я согласен с @Charles. На самом деле есть компиляторы, которые встраивают функции в единицы перевода, так что это определенно не из-за ограничений компилятора.
SBI

5
Хотя в этом ответе есть некоторые технические ошибки, он помог мне увидеть, как компилятор работает с файлами заголовков и т. Д.
thecoshman

10

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

Помните, что C и C ++ используют очень упрощенную модель компиляции, когда компилятор всегда видит только одну единицу перевода за раз. (Это не удается для экспорта, что является основной причиной, по которой только один поставщик фактически реализовал это.)


9

inlineКлючевое слово c ++ вводит в заблуждение, оно не означает «встроить эту функцию». Если функция определена как встроенная, это просто означает, что ее можно определять несколько раз, если все определения равны. Совершенно законно для функции, помеченной inlineкак реальная функция, которая вызывается вместо того, чтобы получить код, встроенный в точку, где она вызывается.

Определение функции в файле заголовка необходимо для шаблонов, поскольку, например, шаблонный класс на самом деле не является классом, это шаблон для класса, для которого вы можете сделать несколько вариантов. Чтобы компилятор мог, например, создать Foo<int>::bar()функцию, когда вы используете шаблон Foo для создания класса Foo , фактическое определение Foo<T>::bar()должно быть видимым.


И поскольку это шаблон для класса , он называется не классом шаблона , а шаблоном класса .
SBI

4
Первый абзац совершенно правильный (и я хотел бы подчеркнуть «вводящий в заблуждение»), но я не вижу необходимости в non sequitur в шаблонах.
Thomas Edleson

Некоторые компиляторы будут использовать это как намек на то, что функция, вероятно, может быть встроена, но на самом деле не гарантируется, что она будет встроена только потому, что вы ее объявляете inline(и не декларирование этого inlineгарантирует, что она не будет встроена).
Keith M

4

Я знаю, что это старая ветка, но подумал, что стоит упомянуть externключевое слово. Недавно я столкнулся с этой проблемой и решил следующим образом

helper.h

namespace DX
{
    extern inline void ThrowIfFailed(HRESULT hr);
}

Helper.cpp

namespace DX
{
    inline void ThrowIfFailed(HRESULT hr)
    {
        if (FAILED(hr))
        {
            std::stringstream ss;
            ss << "#" << hr;
            throw std::exception(ss.str().c_str());
        }
    }
}

6
Обычно это не приведет к тому, что функция будет фактически встроена, если вы не используете оптимизацию всей программы (WPO).
Чак Уолбурн,

3

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

#include "file.h"
// Ok, now me (the compiler) can see the definition of that inline function. 
// So I'm able to replace calls for the actual implementation.

1

Встроенные функции

В C ++ макрос - это не что иное, как встроенная функция. Итак, теперь макросы находятся под управлением компилятора.

  • Важно : Если мы определим класс функции внутри он будет Инлайн автоматически

Код встроенной функции заменяется в том месте, где она вызывается, что снижает накладные расходы на вызов функции.

В некоторых случаях встраивание функции не может работать, например

  • Если статическая переменная используется внутри встроенной функции.

  • Если функция сложная.

  • Если рекурсивный вызов функции

  • Если адрес функции взят неявно или явно

Функция, определенная вне класса, как показано ниже, может стать встроенной

inline int AddTwoVar(int x,int y); //This may not become inline 

inline int AddTwoVar(int x,int y) { return x + y; } // This becomes inline

Функция, определенная внутри класса, также становится встроенной

// Inline SpeedMeter functions
class SpeedMeter
{
    int speed;
    public:
    int getSpeed() const { return speed; }
    void setSpeed(int varSpeed) { speed = varSpeed; }
};
int main()
{
    SpeedMeter objSM;
    objSM.setSpeed(80);
    int speedValue = A.getSpeed();
} 

Здесь обе функции getSpeed ​​и setSpeed ​​станут встроенными


да, возможно, неплохая информация, но на самом деле я не пытаюсь объяснить почему . Может быть, да, но просто не проясняете это.
thecoshman

2
Следующее утверждение неверно: «Важно: если мы определим функцию внутри класса, она автоматически станет встроенной». Даже если вы напишете «встроенную» в объявлении / определении, вы можете быть уверены, что она на самом деле встроена. Даже для шаблонов. Возможно, вы имели в виду, что компилятор автоматически принимает ключевое слово «inline», но не обязано следовать, и я заметил, что в большинстве случаев он не встраивает такие определения в заголовке, даже для простых функций constexpr с основная арифметика.
Пабло Ариэль

Привет, спасибо за комментарии ... Ниже приведены строки из «Размышления на C ++» micc.unifi.it/bertini/download/programmazione/… Страница 400 .. Пожалуйста, проверьте .. Пожалуйста, проголосуйте, если вы согласны. Спасибо ..... Встроенные внутри классов Чтобы определить встроенную функцию, вы должны обычно предшествовать определению функции с помощью ключевого слова inline. Однако внутри определения класса это не обязательно. Любая функция, которую вы определяете внутри определения класса, автоматически становится встроенной.
Саураб Раут

Авторы этой книги могут утверждать, что хотят, потому что они пишут книги, а не код. Это то, что мне пришлось глубоко проанализировать, чтобы мои портативные 3D-демонстрации умещались менее чем в 64 КБ, максимально избегая встроенного кода. Программирование - это факты, а не религия, поэтому не имеет значения, сказал ли это в книге какой-нибудь «бог-программист», если это не отражает того, что происходит на практике. И большинство книг по C ++ представляют собой сборник плохих советов, в которых время от времени вы можете найти какой-нибудь изящный трюк, который можно добавить в свой репертуар.
Пабло Ариэль

Привет, @PabloAriel, спасибо ... Пожалуйста, проанализируйте и дайте мне знать ... Я могу обновить этот ответ согласно анализу
Саураб Раут
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.