GNU GCC (g ++): Neden birden çok yönlendirici üretiyor?


91

Geliştirme ortamı: GNU GCC (g ++) 4.1.2

Birim testinde 'kod kapsamını - özellikle işlev kapsamını' nasıl artıracağımı araştırmaya çalışırken, bazı sınıf dtorlarının birden çok kez üretildiğini buldum. Bazılarınızın neden olduğuna dair bir fikri var mı?

Aşağıdaki kodu kullanarak yukarıda bahsettiğim şeyi denedim ve gözlemledim.

"Test.h" içinde

class BaseClass
{
public:
    ~BaseClass();
    void someMethod();
};

class DerivedClass : public BaseClass
{
public:
    virtual ~DerivedClass();
    virtual void someMethod();
};

"Test.cpp" içinde

#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;
}

Yukarıdaki kodu oluşturduğumda (g ++ test.cpp -o test) ve sonra ne tür sembollerin üretildiğini aşağıdaki gibi görünce,

nm - sınır testi

Aşağıdaki çıktıyı görebiliyordum.

==== 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()

Sorularım aşağıdaki gibidir.

1) Neden birden çok dtor oluşturuldu (BaseClass - 2, DerivedClass - 3)?

2) Bu dansçılar arasındaki fark nedir? Bu çoklu dişliler seçici olarak nasıl kullanılacak?

Şimdi, C ++ projesi için% 100 işlev kapsamı elde etmek için, bunu anlamamız gerektiğine dair bir his var, böylece birim testlerimde tüm bu yönlendiricileri çağırabilirim.

Biri bana yukarıdaki cevabı verebilirse çok memnun olurum.


5
Minimal, eksiksiz bir örnek programı dahil etmek için +1. ( sscce.org )
Robᵩ

2
Temel sınıfınız kasıtlı olarak sanal olmayan bir yıkıcıya sahip mi?
Kerrek SB

2
Küçük bir gözlem; günah işlediniz ve BaseClass yıkıcınızı sanal yapmadınız.
Lyke

Eksik örneğim için özür dilerim. Evet, BaseClass sanal yıkıcıya sahip olmalıdır, böylece bu sınıf nesneleri polimorfik olarak kullanılabilir.
Smg

1
@Lyke: Pekala, türetilmiş bir tabana işaretçi aracılığıyla silinmeyeceğini biliyorsanız sorun değil, sadece emin oluyordum ... komik bir şekilde, temel üyeleri sanal yaparsanız, eşit daha fazla yıkıcı.
Kerrek SB

Yanıtlar:


75

İlk olarak, bu işlevlerin amaçları Itanium C ++ ABI'de açıklanmıştır ; "temel nesne yıkıcı", "tam nesne yıkıcı" ve "yıkıcıyı silme" altındaki tanımlara bakın. Karıştırılan adların eşleştirilmesi 5.1.4'te verilmiştir.

Temel olarak:

  • D2, "temel nesne yıkıcıdır". Veri üyelerini ve sanal olmayan temel sınıfların yanı sıra nesnenin kendisini de yok eder.
  • D1 "tam nesne yıkıcıdır". Ayrıca sanal temel sınıfları da yok eder.
  • D0, "silinen nesne yıkıcıdır". Tüm nesne yıkıcının yaptığı her şeyi yapar, ayrıca operator deletebelleği gerçekten boşaltmaya çağırır .

Sanal temel sınıfınız yoksa, D2 ve D1 aynıdır; GCC, yeterli optimizasyon düzeylerinde, sembolleri her ikisi için de aynı koda değiştirecektir.


Net cevap için teşekkürler. Artık sanal kalıtım türlerine çok aşina olmadığım için daha fazla çalışmam gerekse de ilişki kurabilirim.
Smg

@Smg: sanal kalıtımda, "sanal olarak" miras alınan sınıflar, yalnızca en çok türetilmiş nesnenin sorumluluğu altındadır. Yani varsa, olduğu struct B: virtual Ao ve struct C: Bbir imha zaman sonra, Bçağırmak size B::D1hangi çağırmak sırayla A::D2ve imha zaman Csizi çağırmak C::D1hangi çağırmak B::D2ve A::D2(not nasıl B::D2değil bir destructor çağırmak yapar). Bu alt bölümde gerçekten şaşırtıcı olan şey, tüm durumları 3 yıkıcıdan oluşan basit bir doğrusal hiyerarşi ile yönetebilmektir .
Matthieu M.

Hmm, konuyu tam olarak anlamamış olabilirim ... İlk durumda (B nesnesini yok ederek), A :: D2 yerine A :: D1 çağrılacağını düşündüm. Ayrıca ikinci durumda (C nesnesini yok ederek), A :: D2 yerine A :: D1 çağrılacaktır. Yanlış mıyım?
Smg

A :: D1 çağrılmaz çünkü burada A en üst düzey sınıf değildir; A'nın sanal temel sınıflarını yok etme sorumluluğu (var olabilir veya olmayabilir) A'ya değil, üst düzey sınıfın D1 veya D0'ına aittir.
bdonlan

37

Genellikle kurucunun iki çeşidi ( sorumlu değil / sorumlu ) ve yıkıcının üç çeşidi vardır ( sorumlu değil / sorumlu / sorumlu silme ).

-De- olmayan başka bir sınıftan devralır kullanılarak bir sınıfın bir nesnesi tutarken ctor ve dtor kullanılan virtualnesne (tam bir nesne olmadığı zaman, anahtar kelime mevcut nesnenin oluşturulması ya da imha "not sorumlu" yani sanal temel nesne). Bu ctor, sanal temel nesneye bir işaretçi alır ve onu depolar.

De- ctor ve dtors Diğer tüm durumlarda, yani dahil hiçbir sanal miras varsa içindir; Sınıfın sanal bir yıkıcısı varsa, sorumlu silme işaretçisi vtable yuvasına girerken, nesnenin dinamik türünü bilen bir kapsam (yani otomatik veya statik depolama süresi olan nesneler için) sorumlu dtoru kullanır (çünkü bu hafıza serbest bırakılmamalıdır).

Kod örneği:

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);

Sonuçlar:

  • Dtor için vtables her giriş foo, bazve quuxkarşılık gelen nokta olarak şarj silme dtor.
  • b1ve b2inşa edilir baz() içinde şarj aramaları foo(1) içinde şarj
  • q1ve q2inşa edilir quux() içinde şarj düşer, foo(2) şarj-in ve baz() olmayan-de- bir işaretçi ile foodaha önce yapılan nesne
  • q2tahrip olmuştur ~auto_ptr() -görevli sanal dtor çağırır ~quux() içinde şarj silme çağrıları ~baz() değil-de- , ~foo() in-şarj ve operator delete.
  • q1tahrip olmuştur ~quux() -sorumlu aramaları ~baz() olmayan şarj içinde ve ~foo() içinde şarj
  • b2~auto_ptr() sorumlu tarafından imha edilir , sorumlu sanal dtoru ~baz() sorumlu silmeyi çağırır ~foo() , sorumlu çağırır veoperator delete
  • b1tahrip olmuştur ~baz() içinde şarj aramaları ~foo() içinde şarj

Doğan herkes quuxonun kullanırsınız değil-de- ctor ve dtor ve yaratma sorumluluğu üstlenmesi foonesneyi.

Prensip olarak, sanal temeli olmayan bir sınıf için sorumlu olmayan varyant hiçbir zaman gerekli değildir; bu durumda, içinde şarj varyantı daha sonra kimi kez adlandırıldığı birleşik ve / veya her ikisi için semboller sorumlu in ve olmayan-de- tek uygulanmasına etkiyen.


Anlaşılması kolay bir örnekle birlikte net açıklamanız için teşekkür ederiz. Sanal kalıtımın söz konusu olması durumunda, sanal temel sınıf nesnesi yaratmak en çok türetilmiş sınıfların sorumluluğudur. En türetilmiş sınıf dışındaki diğer sınıflara gelince, bunların sanal temel sınıfa dokunmaması için sorumlu olmayan kurucu tarafından yorumlanması gerekir.
Smg

Kristal netliğinde açıklama için teşekkürler. Daha fazla şey hakkında açıklama almak istedim, ya auto_ptr kullanmazsak ve bunun yerine yapıcıda bellek ayırırsak ve yıkıcıda silersek. Bu durumda sorumlu olmayan / sorumlu silinen yalnızca iki yıkıcı mı olur?
nonenone

1
@bhavin, hayır, kurulum tamamen aynı kalıyor. Bir yıkıcı için üretilen kod, her zaman nesnenin kendisini ve tüm alt nesneleri yok eder, böylece deleteifadenin kodunu ya kendi yıkıcınızın bir parçası olarak ya da alt nesne yıkıcı çağrılarının bir parçası olarak alırsınız . deleteBiz bulmak sanal yıkıcı (varsa ifade nesnenin vtable aracılığıyla çağrısı olarak ya uygulanmaktadır içinde şarj silme veya nesneye doğrudan çağrısı olarak var içinde şarj . Yıkıcı
Simon Richter

Bir deleteifade hiçbir zaman sorumlu olmayan varyantı çağırmaz, bu yalnızca sanal kalıtımı kullanan bir nesneyi yok ederken diğer yıkıcılar tarafından kullanılır.
Simon Richter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.