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), а затем посмотрел, какие символы были сгенерированы следующим образом,

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) Какая разница между этими dtors? Как будут использоваться эти множественные dtors?

У меня теперь есть ощущение, что для достижения 100% -ного покрытия функций для проекта С++ нам нужно было бы это понять, чтобы я мог вызывать все эти dtors в своих модульных тестах.

Я был бы очень признателен, если бы кто-нибудь мог дать мне ответ на вышеупомянутое.

Ответ 1

Во-первых, цели этих функций описаны в ABI Itanium C++; см. определения под "деструктором базового объекта", "деструктором полного объекта" и "удалением деструктора". Соответствие искаженным именам приведено в 5.1.4.

В основном:

  • D2 - "деструктор базового объекта". Он уничтожает сам объект, а также члены данных и не виртуальные базовые классы.
  • D1 является "полным деструктором объекта". Это дополнительно уничтожает виртуальные базовые классы.
  • D0 - это "уничтожающий объект деструктор". Он делает все, что делает полный деструктор объекта, плюс вызывает operator delete, чтобы фактически освободить память.

Если у вас нет виртуальных базовых классов, D2 и D1 идентичны; GCC, на достаточных уровнях оптимизации, на самом деле совмещает символы для одного и того же кода для обоих.

Ответ 2

Обычно существует два варианта конструктора (не входящий/неактивный) и три деструктора (не входящий/неактивный/перезарядный).

Неактивные 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 в каждом из vtables для foo, baz и quux указывает на соответствующую перезагрузку dtor.
  • b1 и b2 строятся с помощью baz(), который называет foo(1) in-charge
  • q1 и q2 строятся с помощью quux() заряда, который падает foo(2) в заряда и baz() не отвечает с указателем на объект foo, который он ранее построил
  • q2 разрушается с помощью ~auto_ptr(), который вызывает виртуальную dtor ~quux() перезарядку, которая называет ~baz() неуправляемым, ~foo() и operator delete.
  • q1 разрушается ~quux(), который называет ~baz() неуправляемым и ~foo() включенным
  • b2 уничтожается с помощью ~auto_ptr(), который вызывает виртуальную dtor ~baz() перезарядку, которая вызывает ~foo() в заряда и operator delete
  • b1 разрушается ~baz(), который называет ~foo() оперативным

Любой, получающий из quux, будет использовать свои неуправляемые ctor и dtor и берет на себя ответственность за создание объекта foo.

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