Почему этот виртуальный деструктор запускает неразрешенный внешний?
Рассмотрим следующее:
В X.h:
class X
{
X();
virtual ~X();
};
x.cpp:
#include "X.h"
X::X()
{}
Попробуйте создать это (я использую цель .dll, чтобы избежать ошибки в отсутствующем главном, и я использую Visual Studio 2010):
Ошибка 1 ошибка LNK2001: неразрешенный внешний символ "private: virtual __thiscall X:: ~ X (void)" (?? 1X @@EAE @XZ)
Небольшие изменения приводят к успешной сборке, однако:
X.h:
class X
{
inline X(); // Now inlined, and everything builds
virtual ~X();
};
или
X.h:
class X
{
X();
~X(); // No longer virtual, and everything builds
};
Что вызывает нерешенную внешнюю ссылку в компоновщике, когда виртуальный диск является виртуальным или когда документ не встроен?
EDIT:
Или, возможно, более интересно, почему я не получаю нерешенную внешность, если я делаю деструктор не виртуальным, или если я встраиваю конструктор?
Ответы
Ответ 1
Ситуация 1:
У вас есть код для конструктора.
Поэтому он создает конструктор в объектный файл. Конструктор нуждается в адресе деструктора для размещения в виртуальной таблице, потому что он не может найти конструктор, который невозможно построить.
Ситуация 2: (встроенный конструктор)
Компилятор решает, что ему не нужно строить конструктор (как он будет встроен).
Таким образом, он не устанавливает какой-либо код и, следовательно, не нуждается в адресе деструктора.
Если вы инициируете объект типа X, он снова будет жаловаться.
Ситуация 3: (не виртуальный деструктор)
Для создания конструктора вам не нужен адрес деструктора.
Поэтому он не жалуется.
Он будет жаловаться, если вы создаете экземпляр объекта типа X.
Ответ 2
В С++ функции должны быть определены тогда и только тогда, когда они используются в вашей программе (см. ODR в 3.2/2). В общем случае не виртуальные функции используются, если они вызываются из потенциально оцениваемых выражений. Любая нечистая виртуальная функция считается безоговорочно используемой. Когда используются [не виртуальные] специальные функции-члены в выделенных местах языкового стандарта. И так далее.
-
В примере первый вы объявили свой деструктор как нечистую виртуальную функцию. Это немедленно означает, что ваш деструктор используется в вашей программе. Это, в свою очередь, означает, что требуется определение этого деструктора. Вы не смогли предоставить определение, поэтому компилятор сообщил об ошибке.
-
В третьем примере деструктор не виртуальный. Поскольку вы не используете деструктор в своей программе, определение не требуется и компиляция кода (см. 12.4 для подробного описания того, что представляет собой использование деструктора).
-
В вашем втором примере вы имеете дело с причудой реализации, вызванной тем, что конструктор встроен. Поскольку деструктор не является чистым виртуальным, требуется определение. Однако ваш компилятор не смог обнаружить ошибку, поэтому код, похоже, успешно компилируется. Вы можете копать причины этого поведения в деталях реализации, но с точки зрения С++ этот пример так же разбит, как и первый по той же причине.
Ответ 3
Вам нужно предоставить тело виртуальному деструктору:
class X
{
X();
virtual ~X() {}
};
Ответ 4
Ответ на ваш первый вопрос,
Что вызывает неразрешенный внешний компоновщик, когда .dtor является виртуальным или когда документ не вложен?
... довольно просто, что у вас нет определения деструктора.
Теперь ваш второй вопрос несколько интереснее:
почему я не получаю неразрешенный внешний, если я создаю деструктор не виртуальный, или если я встраиваю конструктор?
И причина в том, что ваш компилятор не нуждался в деструкторе X
, так как вы никогда не создавали экземпляр X
, поэтому он выбросил весь ваш класс. Если вы попытаетесь скомпилировать эту программу, вы получите неразрешенный внешний файл:
class X
{
public:
X();
~X();
};
X::X() {};
int main()
{
X x;
return 0;
}
Но если вы закомментируете X x;
, он будет компилироваться просто отлично, как вы заметили.
Теперь вернемся к тому, почему он не будет компилироваться, если деструктор if virtual
. Я размышляю здесь, но я считаю, что причина в том, что, поскольку у вас есть виртуальный деструктор, X
теперь является полиморфным классом. Для составления полиморфных классов в памяти компиляторам, реализующим полиморфизм с использованием vtable, нужны адресаты для каждой виртуальной функции. Вы не реализовали X::~X
, поэтому нерешенные внешние результаты.
Почему компилятор просто не отбрасывает X
, как это было, когда X
не был полиморфным классом? Больше спекуляций здесь. Но я ожидаю, что причина в том, что даже если у вас нет прямого экземпляра X
, он не может быть уверен, что нигде в вашем коде нет X
live, masqerading как что-то еще. Например, рассмотрим абстрактный базовый класс. В этом случае вы никогда не создадите экземпляр Base
напрямую, а код для Derived
может быть в отдельной отдельной единицы перевода. Поэтому, когда компилятор попадает в этот полиморфный класс, он не может отменить его, даже если он не знает, что вы его создали.
Ответ 5
Это еще не полная программа (или даже полная DLL). Когда вы получаете сообщение об ошибке, вам действительно помогает, потому что X неприменим без определения для ~ X()
Все это означает, что этот конкретный экземпляр компилятора в некоторых случаях нуждался в определении для него. Даже если он компилируется, он ничего не делает.
Ответ 6
У меня есть подозрение в этом, что это поведение, определяемое реализацией. Вот почему
$10.3/8- "Объявленная виртуальная функция в классе должны быть определены или объявленный чистым (10.4) в этом классе, или и то и другое; но диагностика не требуется (3.2)".
GCC дает ошибку, такую как ниже, что опять же очень наводит на размышления (по крайней мере, мне) о нестандартной детализации реализации виртуальных функций
/home/OyXDcE/ccS7g3Vl.o: функция X::X()': prog.cpp:(.text+0x6):
undefined reference to
vtable для X ' /home/OyXDcE/ccS 7g3Vl.o: В функции X::X()': prog.cpp:(.text+0x16):
undefined reference to
vtable для X ' collect2: ld возвращен 1 статус выхода
Я запутался, если из компилятора для кода OP действительно требуется диагностика, поэтому подумал о публикации этого вопроса, даже если я рискую downvotes:). Конечно, хороший компилятор должен угадать.
Ответ 7
Возможно, вам это не удастся, потому что и constr, и destr являются частными - если в вашей сборке нет другого ref для класса X, тогда компилятор может вывести, что destr не требуется, поэтому отсутствие определения не является Biggie.
Это не объясняет мне, почему случай 1 терпит неудачу, хотя 2 и 3 строят ОК, хотя. Интересно, что произойдет, если обе станут общедоступными?