Среда разработки: 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), а затем посмотрел, какие символы были сгенерированы следующим образом,
nm - тест выпрямления
Я мог видеть следующий результат.
==== 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) Чем отличаются эти дторы? Как эти множественные дторы будут использоваться выборочно?
Теперь у меня есть ощущение, что для достижения 100% покрытия функций для проекта C ++ нам необходимо это понять, чтобы я мог вызывать все эти dtors в своих модульных тестах.
Я был бы очень признателен, если бы кто-нибудь мог дать мне ответ на вышеизложенное.
источник
Ответы:
Во-первых, назначение этих функций описано в Itanium C ++ ABI ; см. определения в разделах «деструктор базового объекта», «полный деструктор объекта» и «деструктор удаления». Сопоставление с искаженными именами приведено в 5.1.4.
В принципе:
operator delete
фактическое освобождение памяти.Если у вас нет виртуальных базовых классов, D2 и D1 идентичны; GCC при достаточных уровнях оптимизации фактически присваивает символы одному и тому же коду для обоих.
источник
struct B: virtual A
and thenstruct C: B
, то при уничтоженииB
вы вызываете,B::D1
который по очереди вызывает,A::D2
а при уничтоженииC
вы вызываете,C::D1
который вызываетB::D2
иA::D2
(обратите внимание, какB::D2
не вызывает деструктор A). Что действительно удивительно в этом подразделении, так это возможность управлять всеми ситуациями с помощью простой линейной иерархии из 3 деструкторов.Обычно существует два варианта конструктора ( не отвечающий / не отвечающий ) и три варианта деструктора ( не отвечающее / ответственное / ответственное удаление ).
Не в заряде т х р и dtor используются при работе с объектом класса , который наследует от другого класса , используя
virtual
ключевое слово, когда объект не является законченным объектом (так текущий объект «не отвечает» построение или разрушающему виртуальный базовый объект). Этот 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);
Полученные результаты:
foo
,baz
иquux
точки в соответствующей в заряде удаления записей dtor.b1
иb2
построеныbaz()
ответственными , которые называютfoo(1)
ответственныхq1
иq2
создаютсяquux()
ответственным , которыйfoo(2)
отвечает иbaz()
не отвечает с указателем наfoo
объект, созданный ранее.q2
уничтожается~auto_ptr()
in-charge , которое вызывает удаление виртуального dtor-~quux()
in-charge , которое вызывает~baz()
not-in-charge ,~foo()
in-charge иoperator delete
.q1
разрушается~quux()
ответственным , который вызывает~baz()
незаправленные и~foo()
ответственныеb2
уничтожается~auto_ptr()
ответственным , который вызывает~baz()
удаление ответственного виртуального dtor , которое вызывает~foo()
ответственный иoperator delete
b1
разрушаются путем~baz()
в заряде , который вызывает~foo()
в зарядуЛюбой, кто производный от,
quux
будет использовать его не отвечающий за ctor и dtor и взять на себя ответственность за созданиеfoo
объекта.В принципе, бесплатный вариант никогда не нужен для класса, у которого нет виртуальных баз; в этом случае, в заряде вариант , то иногда называют унифицирован , и / или символы для обоего в заряде и не-в-заряде являются псевдонимами к одной реализации.
источник
delete
выражения либо как часть вашего собственного деструктора, либо как часть вызовов деструктора подобъекта.delete
Выражение реализуется либо как вызов через таблицу виртуальных объекта , если он имеет виртуальный деструктор (где мы находим в заряду удаление , или как прямой вызов объекта в обязанности деструктор.delete
Выражение никогда не называет не-в-заряда вариант, который используется только другими деструкторов, уничтожая объект , который использует виртуальное наследование.