GNU GCC (g ++): почему он генерирует несколько dtors? - PullRequest
82 голосов
/ 07 июля 2011

Среда разработки: GNU GCC (g ++) 4.1.2

Хотя я пытаюсь исследовать, как увеличить «охват кода - особенно покрытие функций» в модульном тестировании, я обнаружил, что некоторые из классовДтор, кажется, генерируется несколько раз.Кто-нибудь из вас имеет представление о том, почему, пожалуйста?

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

В "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 --demangle 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) Почему было создано несколько дторов(BaseClass - 2, DerivedClass - 3)?

2) В чем разница между этими dtors?Как выборочно будут использоваться эти несколько dtors?

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

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

Ответы [ 2 ]

66 голосов
/ 07 июля 2011

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

В основном:

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

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

36 голосов
/ 07 июля 2011

Обычно есть два варианта конструктора ( без оплаты / без оплаты ) и три деструктора ( без оплаты / в процессе / в процессе удаления ).

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

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

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

...