Чому в заголовку вбудовані функції C ++?


120

Примітка. Це не питання про те, як використовувати вбудовані функції або як вони працюють, більше чому вони зроблені так, як є.

Декларація функції члена класу не потребує визначення функції як 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 я б сказав, що друге посилання моє бути подібним, але я запитую більше про логіку, чому Inline працює так, як це робиться.
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
Перший абзац цілком правильний (і я хотів би, щоб я наголосив на "введенні в оману"), але я не бачу необхідності в непослідовності шаблонів.
Томас Едлсон

Деякі компілятори будуть використовувати це як натяк на те, що функцію, ймовірно, можна накреслити, але дійсно, вона не гарантується, що вона буде вбудована лише тому, що ви її заявляєте inline(а також не заявляєте, що це не inlineгарантує, що вона не буде вкладена).
Кіт М

4

Я знаю, що це стара тема, але думав, що я повинен згадати це externключове слово. Я нещодавно зіткнувся з цим питанням і вирішив так

Помічник.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 замінюється в тому місці, куди він викликається, тому він зменшує накладні витрати функції виклику.

У деяких випадках вбудовування функції не може працювати, наприклад

  • Якщо статична змінна використовується всередині вбудованої функції.

  • Якщо функція складна.

  • Якщо рекурсивний виклик функції

  • Якщо адреса функції приймається неявно або чітко

Функція, визначена поза класом, як показано нижче, може стати вбудованою

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" в декларації / визначенні, ви можете бути впевнені, що вона насправді є вбудованою. Навіть не для шаблонів. Можливо, ви мали на увазі, що компілятор автоматично бере на себе ключове слово "inline", але це не повинно дотримуватися, і я помітив, що в більшості випадків він не вбудовує такі визначення в заголовку, навіть не для простих функцій constexpr основна арифметика.
Пабло Аріель

Привіт, дякую за коментарі ... Нижче наведено рядки з "Думки в C ++ micc.unifi.it/bertini/download/programmazione/…" Сторінка 400 .. Будь ласка, перевірте. Будь ласка, підкажіть, чи згодні ви. Дякую ..... Вбудовані всередину класів Щоб визначити функцію вбудованого, вам потрібно попередньо визначити визначення функції за допомогою ключового слова вбудованого. Однак усередині визначення класу це не обов'язково. Будь-яка функція, яку ви визначаєте всередині визначення класу, автоматично є вбудованою.
Saurabh Raoot

Автори цієї книги можуть вимагати, що вони хочуть, тому що вони пишуть книги, а не кодують. Це те, що мені довелося глибоко проаналізувати, щоб зробити мою портативну 3d демонстрацію вмістом менше 64 кб, уникаючи вкладеного коду максимально. Програмування стосується фактів, а не релігії, тому насправді не має значення, чи якийсь "богопрограміст" сказав це у книзі, якщо він не представляє, що відбувається на практиці. І в більшості книг C ++ є колекція поганих порад, в якій час від часу ви можете знайти якийсь акуратний трюк, який слід додати до свого репертуару.
Пабло Аріель

Привіт @PabloAriel Дякую ... Будь ласка, проаналізуйте і дайте мені знати. Я в порядку, щоб оновити цю відповідь відповідно до аналізу
Саурабх Раоот
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.