brerlapine
brerlapine

Reputation: 41

Clarifying destructor requirements for data types in a struct in C++

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

Answers (2)

Davis Herring
Davis Herring

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

aschepler
aschepler

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++:

  • make it more obvious what the pattern is for cleanup responsibility
  • save us from nasty and hard to find bugs when new and delete, or other Do/Undo operations, are misused
  • save us from writing a lot of repetitive code in the first place
  • save us from explicitly dealing with extra tricky cases for exception safety, if things that should be automatically undone happen before or during a constructor

So 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

Related Questions