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

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)"
([email protected]@@QAEXXZ) referenced in function _main 
1>C:\Users\Me\Documents\Visual Studio 2012\Projects\inline\Debug\inline.exe 
: fatal error LNK1120: 1 unresolved externals

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

#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.

Ответ 8

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

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

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

Функция Code 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 станут встроенными