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

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 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Важно: если мы определим функцию внутри класса, она автоматически станет 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 ​​станут встроенными

Другие вопросы по тегам