GNU GCC (g++): почему он генерирует несколько dtors?
Среда разработки: GNU GCC (g++) 4.1.2
В то время как я пытаюсь исследовать, как увеличить "покрытие кода - особенно покрытие функций" в модульном тестировании, я обнаружил, что некоторые из классов dtor, похоже, генерируются несколько раз. Кто-нибудь из вас имеет представление о том, почему, пожалуйста?
Я попробовал и заметил то, что упомянул выше, используя следующий код.
В "test.h"
class BaseClass
{
public:
~BaseClass();
void someMethod();
};
class DerivedClass : public BaseClass
{
public:
virtual ~DerivedClass();
virtual void someMethod();
};
В "test.cpp"
#include <iostream>
#include "test.h"
BaseClass::~BaseClass()
{
std::cout << "BaseClass dtor invoked" << std::endl;
}
void BaseClass::someMethod()
{
std::cout << "Base class method" << std::endl;
}
DerivedClass::~DerivedClass()
{
std::cout << "DerivedClass dtor invoked" << std::endl;
}
void DerivedClass::someMethod()
{
std::cout << "Derived class method" << std::endl;
}
int main()
{
BaseClass* b_ptr = new BaseClass;
b_ptr->someMethod();
delete b_ptr;
}
Когда я собрал приведенный выше код (g++ test.cpp -o test), а затем увидел, какие символы были сгенерированы следующим образом,
тест
Я мог видеть следующий вывод.
==== following is partial output ====
08048816 T DerivedClass::someMethod()
08048922 T DerivedClass::~DerivedClass()
080489aa T DerivedClass::~DerivedClass()
08048a32 T DerivedClass::~DerivedClass()
08048842 T BaseClass::someMethod()
0804886e T BaseClass::~BaseClass()
080488f6 T BaseClass::~BaseClass()
Мои вопросы следующие.
1) Почему было сгенерировано несколько dtors (BaseClass - 2, DerivedClass - 3)?
2) В чем разница между этими врачами? Как эти множественные dtors будут использоваться выборочно?
Теперь у меня есть ощущение, что для достижения 100% покрытия функций для проекта C++ нам нужно это понять, чтобы я мог вызывать все эти dtors в своих модульных тестах.
Я был бы очень признателен, если бы кто-то мог дать мне ответ на вышеизложенное.
2 ответа
Во-первых, цели этих функций описаны в Itanium C++ ABI; см. определения под "деструктором базового объекта", "деструктором полного объекта" и "удалением деструктора". Отображение искаженных имен приведено в 5.1.4.
В принципе:
- D2 - "деструктор базового объекта". Он уничтожает сам объект, а также члены данных и не виртуальные базовые классы.
- D1 является "полным деструктором объекта". Это дополнительно уничтожает виртуальные базовые классы.
- D0 - это "уничтожающий объект деструктор". Он делает все, что делает полный деструктор объекта, плюс вызывает
operator delete
на самом деле освободить память.
Если у вас нет виртуальных базовых классов, D2 и D1 идентичны; GCC, на достаточных уровнях оптимизации, на самом деле совмещает символы для одного и того же кода для обоих.
Обычно есть два варианта конструктора (не отвечающий за загрузку / ответственный) и три деструктора (не отвечающий за загрузку / удаление/отвечающий за удаление).
Недопустимые ctor и dtor используются при обработке объекта класса, который наследуется от другого класса с использованием virtual
ключевое слово, когда объект не является полным объектом (поэтому текущий объект "не отвечает" за создание или уничтожение виртуального базового объекта). Этот ctor получает указатель на виртуальный базовый объект и сохраняет его.
Ответственные ctor и dtors предназначены для всех остальных случаев, т. Е. Если не задействовано виртуальное наследование; если класс имеет виртуальный деструктор, ответственный указатель удаления dtor попадает в слот vtable, в то время как область видимости, которая знает динамический тип объекта (т. е. для объектов с автоматической или статической длительностью хранения), будет использоватьответственный dtor (потому что эта память не должна быть освобождена).
Пример кода:
struct foo {
foo(int);
virtual ~foo(void);
int bar;
};
struct baz : virtual foo {
baz(void);
virtual ~baz(void);
};
struct quux : baz {
quux(void);
virtual ~quux(void);
};
foo::foo(int i) { bar = i; }
foo::~foo(void) { return; }
baz::baz(void) : foo(1) { return; }
baz::~baz(void) { return; }
quux::quux(void) : foo(2), baz() { return; }
quux::~quux(void) { return; }
baz b1;
std::auto_ptr<foo> b2(new baz);
quux q1;
std::auto_ptr<foo> q2(new quux);
Результаты:
- Запись dtor в каждой из таблиц для
foo
,baz
а такжеquux
указать на соответствующийответственный за удаление dtor. b1
а такжеb2
построеныbaz()
ответственный, который вызываетfoo(1)
ответственныйq1
а такжеq2
построеныquux()
ответственный, который падаетfoo(2)
ответственный иbaz()
не отвечает с указателем наfoo
объект, который он построил ранееq2
разрушается~auto_ptr()
ответственный, который вызывает виртуальный дтор~quux()
удаление за плату, которое вызывает~baz()
не отвечает,~foo()
ответственный иoperator delete
,q1
разрушается~quux()
ответственный, который вызывает~baz()
не ответственный и~foo()
ответственныйb2
разрушается~auto_ptr()
ответственный, который вызывает виртуальный дтор~baz()
удаление за плату, которое вызывает~foo()
ответственный иoperator delete
b1
разрушается~baz()
ответственный, который вызывает~foo()
ответственный
Тот, кто происходит отquux
будет использовать свои несуществующие ctor и dtor и взять на себя ответственность за создание foo
объект.
В принципе, не требующий оплаты вариант никогда не нужен для класса, который не имеет виртуальных баз; в этом случае вариант, связанный с начислением платы, иногда называют унифицированным, и / или символы как ответственного, так и не отвечающего назначаются для единой реализации.