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 объект.

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

Другие вопросы по тегам