C++: rules of destructor declaration
A C++ class destructor is responsible to release the resource this class allocates during its lifetime. For a class that does not allocate any resource explicitly, it's often neglected by the class implementor and compilers merrily generate an implicit public destructor that is often suffice.
Things become a little more complicated when a class does allocate resource and needs to release it during destruction. The first rule that concerns C++ destructor is called 'rule of three', which states if one must define a destructor, one must also define copy constructor and copy assignment operator. Vice versa, if one of those three functions needs to be defined, then all three need to be defined. This rule ensures proper resource management to avoid resource leak or dangling resource.
The 'rule of three' is best applied to a concrete class that sports no inheritance or polymorphic behavior. Otherwise the rule becomes more complicated. One popular rule concerning base class destructor is that 'base class destructor must be virtual'. In fact compilers such as GNU C++ compiler issue diagnostics when it detects a base class destructor is not defined virtual.
However, as all rules, it's important to understand the intention and applicability of a rule and to act best according to its spirit. The intention of the fore mentioned base destructor rule is to ensure proper resource management when client code deletes a derived class object through a base class pointer. Given this guideline, one could make such an observation that one can simply disallow such erroneous behavior when appropriate. To do this, the base class destructor can be declared protected and non-virtual. Once this is done, such a destructor is no longer accessible to client code through delete. The mechanism of delete is explained in a previous entry on this blog. Basically, a delete call performs the following step, 1) call class destructor, polymorphically when its virtual; 2) call class operator delete if it's defined; else call global operator delete if it's defined; else call default std::delete to release the memory this class object occupies.
When a base class destructored is declared protected, client code can no longer delete a base pointer because the base destructor is only accessible to its derived class but not to the client code. Any client code insists doing so will be diagnosed with a compiler error.
Why would we want to this instead of simply following the 'base destructor must be public virtual'?
1) Performance, without virtual function, virtual function dispatching overhead is no longer incurred. This can provide significant performance boost on architectures that supports pipelining, speculative execution, TLBs, paging and caching. Because virtual function dispatching involves pointer indirection, it's likely that page faults will be generated and TLBs flushed and repopulated. Due to the extreme dynamic nature of virtual function dispatching, destination code will only be known at the last point of runtime execution, pipelining and speculative execution will be stalled and results flushed to accommodate immediate code branching.
2) Design, it simply does not make sense to have virtual public destructor when it's known a priori that such a class hierarchy does not manage any kind of resource.
Given these arguments, the second rule of destructor declaration is that 'base class destructor should be either protected non-virtual or public virtual, with protected non-virtual preferred'.
References:
1. Virtuality, Herb Sutter, C/C++ Users Journal, 19(9), September 2001
2. Generic: Change the way you write exeption-safe code---forever, Andrei Alexandrescu and Peru Marginean, C/C++ Users Journal, Dec 01, 2000
Things become a little more complicated when a class does allocate resource and needs to release it during destruction. The first rule that concerns C++ destructor is called 'rule of three', which states if one must define a destructor, one must also define copy constructor and copy assignment operator. Vice versa, if one of those three functions needs to be defined, then all three need to be defined. This rule ensures proper resource management to avoid resource leak or dangling resource.
The 'rule of three' is best applied to a concrete class that sports no inheritance or polymorphic behavior. Otherwise the rule becomes more complicated. One popular rule concerning base class destructor is that 'base class destructor must be virtual'. In fact compilers such as GNU C++ compiler issue diagnostics when it detects a base class destructor is not defined virtual.
However, as all rules, it's important to understand the intention and applicability of a rule and to act best according to its spirit. The intention of the fore mentioned base destructor rule is to ensure proper resource management when client code deletes a derived class object through a base class pointer. Given this guideline, one could make such an observation that one can simply disallow such erroneous behavior when appropriate. To do this, the base class destructor can be declared protected and non-virtual. Once this is done, such a destructor is no longer accessible to client code through delete. The mechanism of delete is explained in a previous entry on this blog. Basically, a delete call performs the following step, 1) call class destructor, polymorphically when its virtual; 2) call class operator delete if it's defined; else call global operator delete if it's defined; else call default std::delete to release the memory this class object occupies.
When a base class destructored is declared protected, client code can no longer delete a base pointer because the base destructor is only accessible to its derived class but not to the client code. Any client code insists doing so will be diagnosed with a compiler error.
Why would we want to this instead of simply following the 'base destructor must be public virtual'?
1) Performance, without virtual function, virtual function dispatching overhead is no longer incurred. This can provide significant performance boost on architectures that supports pipelining, speculative execution, TLBs, paging and caching. Because virtual function dispatching involves pointer indirection, it's likely that page faults will be generated and TLBs flushed and repopulated. Due to the extreme dynamic nature of virtual function dispatching, destination code will only be known at the last point of runtime execution, pipelining and speculative execution will be stalled and results flushed to accommodate immediate code branching.
2) Design, it simply does not make sense to have virtual public destructor when it's known a priori that such a class hierarchy does not manage any kind of resource.
Given these arguments, the second rule of destructor declaration is that 'base class destructor should be either protected non-virtual or public virtual, with protected non-virtual preferred'.
References:
1. Virtuality, Herb Sutter, C/C++ Users Journal, 19(9), September 2001
2. Generic: Change the way you write exeption-safe code---forever, Andrei Alexandrescu and Peru Marginean, C/C++ Users Journal, Dec 01, 2000
<< Home