Почему вызов чисто виртуального метода без тела не приводит к ошибке компоновщика? - PullRequest
0 голосов
/ 21 сентября 2018

Сегодня я столкнулся с довольно странным сценарием.При непосредственном вызове чисто виртуального метода в конструкторе интерфейса я получаю неопределенную ошибку ссылки.

class Interface
{
public:    
    virtual void fun() const = 0; 
    Interface(){ fun(); }
};

class A : public Interface
{
public:
    void fun() const override {};
};

int main()
{
    A a;
}

В результате:

prog.cc: In constructor 'Interface::Interface()':
prog.cc:5:22: warning: pure virtual 'virtual void Interface::fun() const' called from constructor
5 |     Interface(){ fun(); }
  |                      ^
/tmp/ccWMVIWG.o: In function `main':
prog.cc:(.text.startup+0x13): undefined reference to `Interface::fun() const'
collect2: error: ld returned 1 exit status

Однако, оборачивая вызов fun () вдругой метод, подобный этому:

class Interface
{
public:    
    virtual void fun() const = 0; 
    Interface(){ callfun(); }
    virtual void callfun()
    {
        fun();
    }
};

class A : public Interface
{
public:
    void fun() const override {};
};

int main()
{
    A a;
}

Компилируется очень хорошо и (очевидно) вылетает с чистой ошибкой виртуального вызова.Я протестировал его на последних версиях GCC 8.2.0 и 9.0.0 и Clang 8.0.0.Из них только GCC выдает ошибку компоновщика в первом случае.

Ссылки Wandbox для полного рабочего примера с ошибкой:

РЕДАКТИРОВАТЬ: я отмечен для дублирования, но я не уверен, как этот вопрос дублируется.Это не имеет никакого отношения к опасностям вызова чисто виртуального метода (из конструктора или еще чего-нибудь), я их знаю.

Я пытался понять, почему компилятор разрешает этот вызов в одном сценарии, и не делает этого в другом, что очень хорошо объяснил Адам Невраумонт.

EDIT2: Кажется, чтодаже если callFun не является виртуальным, он все равно каким-то образом не позволяет GCC переориентировать и встроить вызов fun.См. Пример ниже:

class Interface
{
public:    
    virtual void fun() const = 0; 
    Interface(){ callfun(); }
    void callfun()
    {
        fun();
    }
};

class A : public Interface
{
public:
    void fun() const override {};
};

int main()
{
    A a;
}

1 Ответ

0 голосов
/ 21 сентября 2018

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

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

В первом случае вы получаете ошибку компоновщика, потому что gcc вызывает девиализацию вызова на fun в ctor.Девиртуализированный вызов fun напрямую вызывает чисто виртуальный метод.Это возможно, потому что при построении Interface компилятору известно состояние таблицы виртуальных функций (модификации производного класса к ней еще не происходят).

Во втором случае компилятор может девиртуализировать вызовcallFun из ctor.Но вызов fun изнутри callFun не может быть девиртуализирован, поскольку callFun может быть вызван извне ctor в другом методе.Девиртуализация была бы неправильной в общем случае .

В этом конкретном случае, если компилятор девиртуализировал callFun , а затем встроил его, он мог бы затем девиртуализировать fun в подписанной копии.Но компилятор этого не делает, так что никакой девиртуализации не происходит.

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

void Interface::fun() const {}

в любом месте любого .cpp файла, на который есть ссылка, создаст ссылку на ваш код и будет верным в любом случае.Чистый виртуальный не означает «не имеет реализации» в C ++, он просто означает «производный класс должен обеспечивать переопределение, и для меня законно не иметь реализацию».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...