Наследование С++ - невозможно определить методы в файле cpp

У меня следующая проблема. У меня есть эти 3 файла (я сделал упрощенный пример, но ошибки те же):

foo.hpp

#pragma once
#include <iostream>

class foo
{
protected:
    virtual void bar() const noexcept = 0;

public:
    foo() = default;
    virtual void callbar() = 0;
};

class baz : public foo
{
protected:
    void bar() const noexcept override;

public:
    void callbar();
};

foo.cpp

#include "foo.hpp"

inline void baz::bar() const noexcept { std::cout << "baz::bar()" << '\n'; }

inline void baz::callbar() { bar(); }

main.cpp

#include "foo.hpp"

auto main() -> int
{
    baz b;
    b.callbar();
}

Компилятор (на самом деле компоновщик, я думаю) дает мне следующую ошибку:

foo.cpp
main.cpp
Generating Code...
Microsoft (R) Incremental Linker Version 14.15.26729.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:foo.exe
foo.obj
main.obj
main.obj : error LNK2001: unresolved external symbol "protected: virtual void __cdecl baz::bar(void)const " (?bar@baz@@MEBAXXZ)
main.obj : error LNK2019: unresolved external symbol "public: virtual void __cdecl baz::callbar(void)" (?callbar@baz@@UEAAXXZ) referenced in function main
foo.exe : fatal error LNK1120: 2 unresolved externals

Теперь я обошел это, выполнив одну из этих двух вещей:

  • Удалить ключевое слово inline
  • Оставьте inline как есть, но переместите определения методов в файл .hpp

Если я делаю одну из этих вещей, все работает. Но мой вопрос: почему? В моем реальном коде я действительно хочу, чтобы компилятор встраивал вызовы методов, плюс я хочу, чтобы они были определены в файле .cpp, чтобы сделать мой файл .hpp более понятным. Есть ли решение для этого?

Почему inline в определениях?

Algirdas Preidžius 10.04.2019 16:51

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

dabljues 10.04.2019 16:52

Как вы думаете, что вы получаете, используя inline? Удаление inline — правильное решение.

john 10.04.2019 16:53

@dabljues Вы неправильно понимаете, что делает inline. Удивительно, но это не имеет ничего общего с встраиванием функций.

john 10.04.2019 16:53

Гоша, это неловко. Теперь буду знать, спасибо! Должен ли я тогда удалить/закрыть вопрос?

dabljues 10.04.2019 16:58

Возможный дубликат Встроенная функция-член С++ в файле .cpp

Nellie Danielyan 10.04.2019 17:00
в линию
Aconcagua 10.04.2019 17:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В вашем коде есть ошибка. Согласно cppreference

The definition of an inline function or variable (since C++17) must be present in the translation unit where it is accessed (not necessarily before the point of access).

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

Таким образом, ваши два альтернативных подхода работают, потому что они либо

  • Полностью удалите это условие (при удалении встроенного спецификатора)
  • Сделайте определение видимым

И последнее, но не менее важное: спецификатор C++ inline не имеет ничего общего с встраиванием функций. Существуют и другие, зависящие от компилятора способы запроса фактического встраивания, то есть __forceinline в различных компиляторах.

Спасибо! Ответ этого парня, который @Aconcagua вставил: stackoverflow.com/a/5971755/1312382, предполагает: «Конечно, это в дополнение к подсказке компилятору о том, что функция должна быть встроена в то место, где она используется (избегая накладных расходов на вызов функции)». Так он ошибается тогда?

dabljues 10.04.2019 17:03

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

SergeyA 10.04.2019 17:06

Спасибо, это действительно прояснило!

dabljues 10.04.2019 17:07

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

eerorika 10.04.2019 18:26

@eerorika Я по-прежнему утверждаю, что inline является свойством языка, а не фактическим встраиванием. Например, функция, которая не объявлена ​​встроенной, все равно будет встроенной, если ее определение видимо, при условии, что она является встроенной. Верно и обратное — встроенная функция, которая не является встроенной, не будет встроенной. Итог - спецификатор не связан.

SergeyA 10.04.2019 19:32

@SergeyA Со всем согласен, кроме твоего вывода. Прямоугольник может быть очень широким, но иметь маленькую площадь (если у него достаточно маленькая высота). Прямоугольник может быть очень узким, но иметь большую площадь (если он достаточно высокий). Таким образом, вы можете заключить, что площадь прямоугольника не связана с шириной прямоугольника. Но это был бы ложный вывод: они напрямую связаны. В вашем выводе та же проблема.

eerorika 10.04.2019 21:39

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

SergeyA 10.04.2019 21:42

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