可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Is there any way to call a field destructor before the class destructor?
Suppose I have 2 classes Small
and Big
, and Big
contains an instance of Small
as its field as such:
class Small { public: ~Small() {std::cout << "Small destructor" << std::endl;} }; class Big { public: ~Big() {std::cout << "Big destructor" << std::endl;} private: Small small; }; int main() { Big big; }
This, of course, calls the big destructor before the small destructor:
Big destructor Small destructor
I need the Small
destructor to be called before the Big
destructor since it does some cleanup necessary for the Big
destructor.
I could:
- call the
small.~Small()
destructor explicitly. -> This, however, calls the Small
destructor twice: once explicitly, and once after the Big
destructor has been executed. - have a
Small*
as the field and call delete small;
in the Big
destructor
I am aware that I can have a function in the Small
class that does the cleanup and call it in the Big
destructor, but I was wondering if there was a way to inverse the destructor order.
Is there any better way to do this?
回答1:
Well, I don't know why you want to keep on with this flawing design, but you can solve the problem described in your first bullet using placement new.
It follows a minimal, working example:
#include <iostream> struct Small { ~Small() {std::cout << "Small destructor" << std::endl;} }; struct Big { Big() { ::new (storage) Small; } ~Big() { reinterpret_cast<Small *>(storage)->~Small(); std::cout << "Big destructor" << std::endl; } Small & small() { return *reinterpret_cast<Small *>(storage); } private: unsigned char storage[sizeof(Small)]; }; int main() { Big big; }
You don't have anymore a variable of type Small
, but with something like the small
member function in the example you can easily work around it.
The idea is that you reserve enough space to construct in-place a Small
and then you can invoke its destructor explicitly as you did. It won't be called twice, for all what the Big
class has to release is an array of unsigned char
s.
Moreover, you won't store your Small
into the dynamic storage directly, for actually you are using a data member of your Big
to create it in.
That being said, I'd suggest you to allocate it on the dynamic storage unless you have a good reason to do otherwise. Use a std::unique_ptr
and reset it at the beginning of the destructor of Big
. Your Small
will go away before the body of the destructor is actually executed as expected and also in this case the destructor won't be called twice.
EDIT
As suggested in the comments, std::optional
can be another viable solution instead of std::unique_ptr
. Keep in mind that std::optional
is part of the C++17, so if you can use it mostly depends on what's the revision of the standard to which you must adhere.
回答2:
Without knowing why you want to do this, my only suggestion is to break up Big
into the parts that need to be destroyed after Small
from the rest and then use composition to include that inside Big
. Then you have control over the order of destruction:
class Small { public: ~Small() {std::cout << "Small destructor" << std::endl;} }; class BigImpl { public: ~BigImpl() { std::cout << "Big destructor" << std::endl; } }; class Big { private: BigImpl bigimpl; Small small; };
回答3:
The order of destructor calls cannot be changed. The proper way to design this is that Small
performs its own cleanup.
If you cannot change Small
then you could make a class SmallWrapper
that contains a Small
and also can perform the required cleanup.
The standard containers std::optional
or std::unique_ptr
or std::shared_ptr
might suffice for this purpose.