Virtual call w destruktorze klasy bazowej

Virtual call w destruktorze klasy bazowej
mwl4
  • Rejestracja:około 12 lat
  • Ostatnio:15 dni
  • Lokalizacja:Wrocław
  • Postów:399
0

Czy ktoś może powiedzieć dlaczego to co jest niżej nie działa tak jak powinno?

Kopiuj
#include <cstdio>

class CBase
{
public:
	CBase()
	{
		printf("CBase::CBase()\n");
	}
	virtual ~CBase()
	{
		printf("CBase::~CBase()\n");
		this->Destroy();
	}
	virtual void Destroy()
	{
		printf("CBase::Destroy()\n");
	}
};

class CChild : public CBase
{
public:
	CChild()
	{
		printf("CChild::CChild()\n");
	}
	virtual ~CChild()
	{
		printf("CChild::~CChild()\n");
	}
	virtual void Destroy()
	{
		printf("CChild::Destroy()\n");
	}
};

int main()
{
	printf("a\n");
	CChild a;
	printf("b\n");
	CBase* b = new CChild;
	delete b;
	return 0;
}

Logicznie rzecz biorąc powinno się wykonać Destroy z klasy CChild, ale tak się nie dzieje:

http://ideone.com/LI7GPK


Asm/C/C++
edytowany 1x, ostatnio: mwl4
spartanPAGE
  • Rejestracja:prawie 12 lat
  • Ostatnio:około 13 godzin
4

Zachowanie jest niezdefiniowane.

At the point a class destructor is run, all subclass destructors have already been run. It would not be valid to call a virtual method defined by a subclass, for which its destructor has already run.

Rule of dumb(thumb?): don't call virtuals in constructors & destructors.

//EDIT: Do twojego problemu użyj shared ptr z własnym deleterem (ten konstruktor:)

Kopiuj
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r );

Jednak, jeśli chciałbyś mi zaufać, to dużo lepszym pomysłem jest trzymanie takich rzeczy w stanie biernym - niech odpowiedni menadżer tym zarządza, a dla wyspecjalizowanych menadżerów możesz zrobić adapter(most, lub maszynę stanów, ją też da się upchać), który pozwoli Ci przełączać oGL/D3D w locie.

edytowany 6x, ostatnio: spartanPAGE
Zobacz pozostałe 12 komentarzy
spartanPAGE
@mwl4 Nie mówię nic o trzymaniu tekstur w nieskończoność czy ładowaniu ich bez pomyślunku. Zrobiłeś po prostu niepotrzebnie rozwleczoną implementację zliczających wskaźników :P użyj shared_ptr
mwl4
Potrzebnie, mówiłem już, że ilość referencji w obiekcie jest dla mnie dość ważne. Poza tym, w shared_ptr czy w unique_ptr masz takie coś: _Ty *_Ptr; _Ref_count_base *_Rep; (w implementacji msu). Więc w praktyce wychodzi tak samo a nawet i gorzej, bo wyjściowo pamięć jest rezerwowana oddzielnie dla samego wskaźnika na obiekt i referencję.
kq
polecam std::make_shared, gwarantuje tylko jedną alokację.
KR
@mwl4 Może tylko jedna instrukcja więcej, ale nie jeden takt więcej, tylko potecjalnie kilkanaście taktów więcej jeśli skok zostanie źle przewidziany. Statyczne skoki są za to bardzo dobrze przewidywalne, bo adres docelowy się nie zmienia. Poza tym vcall to praktycznie utrata możliwości inlineowania (z wyjątkiem trywialnych sytuacji) i zamknięcie drogi do innych optymalizacji.
mwl4
@Krolik u mnie i tak nie ma możliwości włączenia optymalizacji ze względu tego co robię.
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:2 dni
  • Lokalizacja:Szczecin
2

@spartanPAGE zachowanie jest w pełni zdefiniowane. Za Biblią:

§12.7 [class.cdtor]/7 napisał(a)

Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class’s non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class.

Inaczej mówiąc, zostanie wywołana funkcja dla istniejącego obiektu najniżej w hierarchii dziedziczenia. Przykład: http://melpon.org/wandbox/permlink/mMT79u0t4KK4oKkm

Gdy miałem ten problem to rozwiązałem go poprzez utworzenie szablonu klasy, którą umieszczałem na końcu hierarchii dziedziczenia, która w destruktorze wykonywała odpowiednie procedury czyszczące. Wymaga to jednak pewnej systematyczności w tworzeniu obiektów.

Kopiuj
struct Interface
{
	virtual ~Interface(){
		DBG("Interface::~Interface()");
	}

	virtual void foo() const = 0;

protected:
	Interface(){}

	void cleanup(){
		DBG("Interface::cleanup()");
	}
};

struct Impl : Interface
{
	virtual void foo() const override {
		DBG("Impl::foo()");
	}

	virtual ~Impl(){
		DBG("Impl::~Impl()");
	}
};

template <typename T>
struct InterfaceCleaner : T
{
	virtual ~InterfaceCleaner(){
		DBG("InterfaceCleaner::~InterfaceCleaner()");
		this->Interface::cleanup();
	}
};

http://melpon.org/wandbox/permlink/Lx2qgyDSmhX6MesR

W przypadku OPa wystarczy jednak std::shared_ptr, a jeśli wydajność jest tak niesamowicie istotna, to może boost::intrusive_ptr? Ewentualnie może EASTL, albo jakieś autorskie rozwiązanie?


spartanPAGE
Zawsze cos namieszam; papierka nie moglem znalezc :s
vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
0

Obejście problemu: http://www.aristeia.com/EC3E/3E_item9.pdf
(fragment "Effective C++", Scott Meyers)

Przykład pokazuje obejście dla konstruktora, ale raczej nie będzie problemu z zamianą tego na wersję z destruktorem.

Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:14 minut
1
Kopiuj
delete a
CChild::~CChild()
CBase::~CBase()
CBase::Destroy()

Kiedy już zakończył się destruktor ~CChild(), CChild-owa część obiektu została zniszczona. Nie może się wykonać metoda wirtualna klasy CChild, bo ta klasa w obiekcie już nie istnieje. (metoda mogłaby odwoływać się do pól klasy pochodnej które już zostały zniszczone)

Podobny efekt się dzieje w konstruktorze: w konstr. bazowym nie wykona się metoda wirtualna klasy pochodnej, bo konstruktor pochodnej nie był wykonany, więc pola do których metoda virt. mogłaby się odwoływać nie zostały jeszcze skonstruowane.

Albo prościej: nie wykona się metoda wirtualna klasy, której konstruktor jeszcze nie był wywołany, albo której destruktor już się wykonał.

1

http://melpon.org/wandbox/permlink/XjCr6q9fQba4aloC

ten compile error powinien wszystko wyjaśnić

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.