не встроенная виртуальная функция, определенная в заголовочном файле

Правило единого определения гласит:
Во всей программе объект
или не встроенная функция не может иметь более одного определения. (от
Википедия)

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

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

Например:

//derived.hpp
#include <iostream>
class Base {
public:
virtual ~Base() {}
virtual void vfunc() {
std::cout << "Base::vfunc()\n";
}
};

class Derived : public Base {
public:
virtual ~Derived() {}

virtual void vfunc() {
std::cout << "Derived::vfunc()\n";
}
};

//foo.cpp

#include "derived.hpp"void func() {
Base* ptr = new Derived();
ptr->vfunc(); //polymorphic call, can't be inlined
delete ptr;

ptr = new Base();
ptr->vfunc();
delete ptr;
}
//main.cpp

#include "derived.hpp"
int main() {
Base* ptr = new Derived();
ptr->vfunc(); //polymorphic call, can't be inlined
delete ptr;

ptr = new Base();
ptr->vfunc();
delete ptr;
return 0;
}

Мне интересно:

vfunc (и dtor) вызывается как в foo.cpp, так и в main.cpp полиморфно (без подстановки), что означает, что он определяется дважды во всей программе, поэтому он нарушает ODR, не так ли? Как это компилируется (ссылка)?

Я только что видел это:

Более одного определения

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

Каковы certain cases? Является ли вышеуказанный случай одним из таких?

3

Решение

vfunc (и dtor) вызывается как в foo.cpp, так и в main.cpp полиморфно (без вставки), что означает, что он определен дважды во всей программе,

Прежде всего: звонить не значит определение. Таким образом, вызовы функций не сообщают вам, нарушен ли ODR.

так что это нарушает ODR, не так ли? Как это компилируется (ссылка)?

Он компилируется, потому что функции-члены, определенные внутри определения класса, неявно объявленный встроенный и, следовательно, не нарушать ODR. Это относится как к vfunc определения, а также для Dtors, так что вы здесь хорошо.

Замечания: Есть разница между функцией объявленный inline (явно или неявно) и функция, которая фактически становится встроенной. Решение компилятора о включении функции однажды может зависеть от inline Ключевое слово, но это было и остается лишь намеком. В наши дни оптимизаторы могут предсказывать лучше, чем любой человек, когда и когда не встраивается, является хорошим выбором, поэтому компилятор может игнорировать эту подсказку всякий раз, когда сочтет нужным, и он может встроить функции, которые имеют не был объявлен встроенным. Так с современными компиляторами, inline это просто средство подчиняться ODR для функций, которые не объявлены inline неявно.

Обновить: Так что в вашем случае функция объявленный встроенный неявно, определение включено в две единицы перевода (два .cpp’s), и это делает не быть встроенным компилятором. В этом случае компоновщик увидит символ для функции дважды, но он не будет жаловаться на несколько символов из-за встроенного объявления.

2

Другие решения

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

Если, с другой стороны, у нас есть что-то вроде этого в заголовочном файле:

class Base {
public:
virtual ~Base() {}
virtual void vfunc();
};

class Derived : public Base {
public:
virtual ~Derived() {}

virtual void vfunc();
};void Base::vfunc()
{
{
std::cout << "Base::vfunc()\n";
}
}

void Derived::vfunc()
{
{
std::cout << "Derived::vfunc()\n";
}
}

Теперь вы нарушаете ODR, так как Derived::vfunc() не объявляется встроенным, и будучи включенным несколько раз, он определяется более одного раза (хотя и с точно таким же определением).

1

По вопросам рекламы [email protected]