Почему этот виртуальный деструктор вызывает нерешенное внешнее? - PullRequest
15 голосов
/ 25 августа 2010

Рассмотрим следующее:

В 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
};

Что является причиной неразрешенного внешнего в компоновщике, когда .dtor является виртуальным или когда .ctor не встроен?

EDIT:

Или, возможно, более интересно, почему я не получаю неразрешенный внешний элемент, если я делаю деструктор не виртуальным или если я строю конструктор?

Ответы [ 7 ]

21 голосов
/ 25 августа 2010

Ситуация 1:

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

Ситуация 2: (встроенный конструктор)

Компилятор решает, что ему не нужно создавать конструктор (так как он будет встроен).
Как таковой он не создает никакого кода и поэтому не нуждается в адресе деструктора.

Если вы создадите экземпляр объекта типа X, он снова будет жаловаться.

Ситуация 3: (не виртуальный деструктор)

Вам не нужен адрес деструктора, чтобы построить конструктор.
Так что не жалуйся.

Он будет жаловаться, если вы создадите экземпляр объекта типа X.

5 голосов
/ 25 августа 2010

В C ++ функции должны быть определены тогда и только тогда, когда они используются в вашей программе (см. ODR в 3.2 / 2).В общем, не виртуальные функции используются , если они вызываются из потенциально вычисляемых выражений.Любая не чистая виртуальная функция безоговорочно считается используемой .Когда [не виртуальные] специальные функции-члены используются, определяется в выделенных местах языкового стандарта.И т. Д.

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

  • В вашем третьем примере деструктор не виртуален.Поскольку вы не используете деструктор в вашей программе, определение не требуется, и код компилируется (подробное описание того, что составляет использование деструктора *, см. В 12.4.

  • В вашем втором примере вы имеете дело с особенностью реализации, вызванной тем, что конструктор встроен.Поскольку деструктор не является чисто виртуальным, определение является обязательным.Однако вашему компилятору не удалось обнаружить ошибку, поэтому код, похоже, успешно компилируется.Причины такого поведения можно найти в деталях реализации, но с точки зрения C ++ этот пример сломан, как и первый, по той же причине.

5 голосов
/ 25 августа 2010

Вам нужно отдать тело виртуальному деструктору:


class X
{
    X();
    virtual ~X() {}
};
2 голосов
/ 25 августа 2010

Ответ на ваш первый вопрос,

Что вызывает нерешенное внешнее в компоновщик, когда .dtor является виртуальным или когда .ctor не встроен?

... просто у вас нет определения деструктора.

Теперь ваш второй вопрос несколько интереснее:

почему я не получаю неразрешенного внешний, если я сделаю деструктор не виртуальный, или если я в строке конструктор?

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

class X
{
public:
    X();
     ~X();
};

X::X() {};

int main()
{
    X x;
    return 0;
}

Но если вы закомментируете X x;, то, как вы заметили, он будет компилироваться просто отлично.

Теперь давайте вернемся к тому, почему он не скомпилируется, если деструктор, если virtual. Я размышляю здесь, но я полагаю, что причина в том, что, поскольку у вас есть виртуальный деструктор, X теперь является полиморфным классом. Чтобы разложить полиморфные классы в памяти, компиляторам, которые реализуют полиморфизм с использованием vtable , нужны адреса для каждой виртуальной функции. Вы не внедрили X::~X, поэтому нерешенные внешние результаты.

Почему компилятор просто не выбрасывает X, как это было, когда X не был полиморфным классом? Больше предположений здесь. Но я ожидаю, что причина в том, что даже если вы непосредственно не создали экземпляр X, нельзя быть уверенным, что нигде в вашем коде не будет X live, маскирующегося под нечто иное. Для примера рассмотрим абстрактный базовый класс. В этом случае вы никогда не создадите экземпляр Base напрямую, а код для Derived может быть в полностью отдельной единице перевода. Поэтому, когда компилятор попадает в этот полиморфный класс, он не может отбросить его, даже если не знает, что вы его создали.

1 голос
/ 25 августа 2010

У меня есть подозрение, что это поведение, определяемое реализацией.Вот почему

$ 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/ccS7g3Vl.o: в функции X::X()': prog.cpp:(.text+0x16): undefined reference to vtable для X' collect2: ld вернул 1 состояние выхода

Я запутался, если от компилятора действительно требуется диагностика для кода OP, так что подумайте об этом, даже если я рискну понизить голос :).Конечно, хороший компилятор, наверное, угадаю.

1 голос
/ 25 августа 2010

Это еще не полная программа (или даже не полная DLL). Когда вы получаете сообщение об ошибке, вам на самом деле помогают, потому что X невозможно использовать без определения ~ X ()

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

0 голосов
/ 25 августа 2010

Возможно, вам это сойдет с рук, потому что и constr, и destr являются частными - если в вашей сборке нет другого ref для класса X, компилятор может сделать вывод, что destr не требуется, поэтому отсутствие определения Biggie.

Это не объясняет мне, почему прецедент 1 терпит неудачу, в то время как 2 и 3 строят все в порядке. Интересно, что случится, если оба станут достоянием общественности?

...