Почему в заголовке есть встроенные функции C ++? - PullRequest
104 голосов
/ 20 февраля 2011

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

Ответы [ 8 ]

104 голосов
/ 20 февраля 2011

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

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

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

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

100 голосов
/ 20 февраля 2011

Есть два способа посмотреть на это:

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

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

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

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

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

20 голосов
/ 20 февраля 2011

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

9 голосов
/ 20 февраля 2011

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

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

8 голосов
/ 20 февраля 2011

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

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

3 голосов
/ 02 мая 2014

Я знаю, что это старая ветка, но подумал, что должен упомянуть ключевое слово 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());
        }
    }
}
1 голос
/ 20 февраля 2011

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

#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.
0 голосов
/ 10 августа 2018

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

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

  • Важно : если мы определим функцию внутри класса, она станет Inline автоматически

Функция Code of Inline заменяется в том месте, где она вызывается, поэтому она снижает накладные расходы на вызывающую функцию.

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

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

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

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

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

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

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 ​​станут встроенными

...