Reputation: 41
Destructors of structs: Could you specify every type of data type that would be have to be explicitly handled in the destructor of a struct?
struct Node {
int val; // representing any/all primitive data types
int* ptrToVal; // representing any/all pointers to primitive data types
int arr[5]; // representing any/all arrays
int* ptrToArr[5]; // representing any/all array of pointers
Node* next; // pointer to a struct
vector<Node> vOfNodes; // vector of structs
vector<Node*> vOfPtrs; // vector of struct pointers
unordered_map<int, Node*> um; //representing any pre-existing class template
// Default constructor
Node() : val(0), ptrToVal(nullptr), arr(), ptrToArr(), next(nullptr),
vOfNodes(), vOfPtrs(), um(){}
//Overloaded constructor
Node(int val, int* toVal, Node* n, vector<Node> vN, vector<Node*> toV,
unordered_map<int, Node*> m)
: val(val), ptrToVal(toVal), arr(), ptrToArr(), next(n),
vOfNodes(vN), vOfPtrs(toV), um(m){}
What would be necessary to add to the destructor of the Node struct? Is there any other tricky data structures that I didn’t couldn’t think of that would also necessitate non-trivial code in the destructor?
Upvotes: 2
Views: 341
Reputation: 40013
Anything could require explicit destruction: an int
could be a file descriptor to be closed, an index into an array (external to the object) identifying an object to be finalized in some way, or a pointer stored as std::uintptr_t
for type erasure reasons. A function pointer could be a registered cleanup function to be called. An object with its own destructor could contain information of any of these types that it doesn’t know how to handle.
On the other hand, a raw object pointer, which is the poster child for explicit destruction, might just be a non-owning (“observing”) pointer into another data structure and need no cleanup at all.
So there’s no predetermined answer: you have to consider why the object has each member and what it owns about each.
Upvotes: 3
Reputation: 72431
What would be necessary to add to the destructor of the Node struct?
Based on the code shown, nothing. Guessing a fair amount, maybe some delete
statements. Ideally, nothing.
A destructor is mainly for "undoing" some earlier action associated with an object when that object's lifetime ends - like the corresponding delete
to a new
, or a file close to an open, or resetting temporary configuration changes back to originals, or etc. But nothing in those members just existing requires any C++ code to undo.
Though some of these members are or contain "ambiguous pointers". We can't tell just by looking how they're to be used: can pointers to any particular objects go there? Only pointers which came from new
expressions? If they're from new
expressions, is the struct "responsible" for cleaning up with an eventual delete
(or delete[]
), or does the code which did the new
expressions still have responsibility for that? These questions could apply to every pointer involved: ptrToVal
, next
, and the elements within ptrToArr
(misleadingly named), vOfPtrs
, and/or um
.
If the answer is that the struct is never responsible for doing delete
, then the "raw pointer" is an okay choice after all, and the struct doesn't need a destructor declared. (C++ will automatically give it a destructor, and define it if needed.)
If the answer is that the struct should do some delete
on some of those pointers, we could define its destructor to do that. But even better, we could change the members from raw pointers to smart pointers, like std::unique_ptr<Node> next;
or std::vector<std::shared_ptr<Node>> vOfPtrs;
. Smart pointers are one type of RAII handle (Resource Allocation Is Initialization) .
RAII handles in C++:
new
and delete
, or other Do/Undo operations, are misusedSo if smart pointers are appropriately used, they take over the responsibility for undoing things, and a struct at the higher level doesn't need a destructor at all.
Then the one remaining reason to write a destructor which is more than just = default;
or {}
is then when implementing a brand new RAII handle, if it's not something just easily done by std::unique_ptr
. And since an RAII handle should hold just one action to undo, that's usually a one-statement destructor.
Upvotes: 1