Встроенная функция-член C ++ в файле .cpp - PullRequest
68 голосов
/ 22 октября 2010

Я знаю, что встроенные функции-члены по определению должны идти в заголовок.Но что, если невозможно поместить реализацию функции в заголовок?Давайте рассмотрим следующую ситуацию:

Файл Ah

#pragma once
#include "B.h"

class A{
    B b;
};

Файл Bh

#pragma once

class A; //forward declaration

class B{
    inline A getA();
};

Из-за циклического включения я должен поместить реализацию getA в

B.cpp

#include "B.h"
#include "A.h"

inline A B::getA(){
    return A();
}

Будет ли встроенный компилятор getA?Если да, какое ключевое слово inline является значимым (в заголовке или в файле .cpp)?Есть ли другой способ поместить определение встроенной функции-члена в ее файл .cpp?

Ответы [ 6 ]

64 голосов
/ 22 октября 2010

Цитирование из C ++ FAQ :

Примечание. Обязательно, чтобы определение функции (часть между {...}) было помещено в заголовочный файл, если толькофункция используется только в одном файле .cpp.В частности, если вы поместите определение встроенной функции в файл .cpp и вызовете его из другого файла .cpp, вы получите «неразрешенную внешнюю» ошибку от компоновщика.

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

Будет ли компилятор встроенным getA?

Нет, кроме случаев использования getA() в самом B.cpp.

Если это так, какое ключевое слово inline является значимым (одно взаголовок или тот, что в cpp)?

Лучшая практика : только в определении вне тела класса.

Есть ли другой способпоместить определение встроенной функции-члена в ее файл cpp?

Нет, по крайней мере, я не знаю.

16 голосов
/ 22 октября 2010

Не может выходить за рамки B.cpp. Компилятор работает на основе каждой единицы компиляции, то есть он компилирует каждый файл .cpp отдельно, поэтому, если он компилирует C.cpp, у него не будет кода для getA (), и ему потребуется выполнить вызов функции пусть компоновщик исправит это (или, если он действительно взял вас за слово и попытался встроить, у него будет ошибка компоновщика. inline имеет те же качества, что и static).

Единственным исключением является LTCG, то есть генерация кода времени соединения, которая доступна на более новых компиляторах.

Один из подходов в этом случае состоит в том, чтобы иметь другой заголовочный файл (иногда называемый * .inl файлами), который содержит встроенный код.

РЕДАКТИРОВАТЬ: Что касается inline является актуальным - это тот, который в определении класса, то есть в файле заголовка. Имейте в виду, что многие компиляторы имеют собственное мнение о том, что можно и нужно указывать. Например, gcc может полностью отключить встраивание (-O0) или встроить все, что считает целесообразным (например, -O3).

8 голосов
/ 22 октября 2010

Я бы пошел об этом в противоположном направлении.

Не добавляйте встроенные объявления в вашу функцию (если вам это не нужно).

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

Xh

class X
{
    public:
        int getX()   { return 4;} // No inline because it is part of the class.
                                  // The compiler knows that needs an inline tag
        int getY();
        int getZ();
};

inline
int X::getY()  { return 5;}       // This needs to be explicitly declared inline.
                                  // Otherwise the linker will complain about
                                  // multiple definitions in compilation units.

X.cpp

 // Never declare anything inline in the cpp file.

 int X::getZ() { return 6; }

Для вас более конкретный случай.
Удалите все встроенные спецификации.Они не делают то, что вы думаете, что они делают.

7 голосов
/ 22 октября 2010

В наши дни большинство компиляторов могут выполнять вставку как во время компоновки, так и во время компиляции. Если ваша функция может извлечь выгоду из встраивания, оптимизатор времени соединения, скорее всего, сделает именно это.

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

0 голосов
/ 09 мая 2018

ИСО / МЭК 14882: 2014

Каждая программа должна содержать ровно одно определение каждой не встроенной функции или переменной, которая используется в этой программе для одрДиагностика не требуется.Определение может явным образом появиться в программе, оно может быть найдено в стандартной или пользовательской библиотеке или (при необходимости) неявно определено (см. 12.1, 12.4 и 12.8).Встроенная функция должна быть определена в каждой единице перевода, в которой она используется odr.

0 голосов
/ 06 июня 2015

Вот как я это сделал.

Файл A.h

#pragma once
#include "B.h"

class A {
    B b;
};

Файл Б.ч

#pragma once

class B {
public:
    template<class T> inline T getA() {
        assert(NULL); // Use 'getA<A>()' template specialization only!
        return NULL;
    }
};

class A; // Forward declaration
template<> inline A B::getA<A>();

Файл C.h

#pragma once
#include "A.h"
#include "B.h"

// Implement template specialization here!
template<> inline A B::getA<A>() { return A(); }

Просто включите файл "C.h", чтобы можно было использовать метод getA (). Единственное изменение в исходном коде заключается в том, что метод getA () должен быть определен как public, а не private.

Однако, как многие из вас объяснили, это не очень полезно.

...