The source code of this library can be found at: https://github.com/BeAllAround/ManualDrop
ManuallyDrop<T> enables you to entirely "drop" the implicit destructor of T RAII with the capability to invoke it explicitly or move it into another RAII-based object.
ManuallyDrop<T> eliminates the destructor call for objects whose lifetime is known to end via transfer of ownership rather than scope exit. This can reduce overhead when destructors are non-trivial or cannot be optimized away, particularly in low-level containers, allocators, and performance-critical code.
This library is a utility for explicitly separating object lifetime from storage lifetime.
One of the recommended use cases is when an object is guaranteed to be moved out of the scope, so that no additional destructor [potentially overhead in some cases] call is invoked for it.
This "potential" overhead mostly involves expensive situations where the ~T() destructor cannot be inlined. E.g. destructors based on system calls, virtual destructors, etc.
So you are saving:
- a destructor call - one branch to the destructor
- the associated call/return overhead - one return
- any work performed by a moved-from destructor - whatever the destructor itself checks
For example,
template<class T>
using Moveable = ManuallyDrop<T>;
{
std::vector<S> v;
Moveable<S> s (10);
v.push_back(
std::move(s)
);
// Output
/*
S(int)
S(S&&)
~S() // Only one destructor invoked by the ~std::vector<S>() "RAII" as the s is guareeted to be moved
*/
}Some libraries don't bother making moved-from destruction cheap.
For example, even after moving, we end up with:
~S() {
unregister_from_global_registry();
}Just dump the ManualDrop.hpp header into your project and you are good to go!
class S {
S();
S(int);
S(const S&);
S(S&&);
S&operator=(const S&);
S&operator=(S&&);
~S();
}
ManuallyDrop<S> s (1); // S(int)
ManuallyDrop<S> s1 = s; // S(const S&)
s.drop(); // Trigger s.~S()
s1.drop(); // Trigger s1.~S()
// Output
/*
S(int)
S(const S&)
~S()
~S()
*/ManuallyDrop<S> s (1);
S s1 = std::move(s); // [Implicit] Contextual conversion to S&& (operator S&&()) triggered here
// Implicit s1.~S() invoked
// Thus, s.drop() is not needed
// Output
/*
S(int)
S(S&&)
~S()
*/int* s_p_i { nullptr };
int* s1_p_i { nullptr };
{
ManuallyDrop<S> s;
ManuallyDrop<S> s1 (1);
s_p_i = s.get_resource_as_pointer()->i_ptr;
s1_p_i = s1.get_resource_as_pointer()->i_ptr;
}
assertm(
"Detached on-heap members of S not cleaned up",
s_p_i == nullptr &&
*(s1_p_i) == 1
);
// Heap deallocation of the detached int*
delete s1_p_i;{
ManuallyDrop<S> s (2);
ManuallyDrop<S> s1 (1);
// DROP REQUIRED (IF INITIALIZED) BEFORE COPY/MOVE ASSIGNMENT SINCE THERE IS NO IMPLICIT DESTRUCTION
s.drop();
s = std::move(s1);
// s1.drop(); // NOT NEEDED ESSENTIALLY AS IT IS MOVED INTO s
s.drop();
}// Compare the RAII outputs of these two from the main.cpp test suite
{
BLOCK("S RAII Output | Vector reallocation move + destructor") {
std::vector<S> v;
v.reserve(10);
for(size_t i = 0; i < 12; i++) {
S item (i);
v.push_back(std::move(item));
}
}
BLOCK("Moveable<S> RAII Output | Vector reallocation move + destructor") {
std::vector<Moveable<S>> v;
v.reserve(10);
for(size_t i = 0; i < 12; i++) {
Moveable<S> item (i);
v.push_back(std::move(item));
}
defer: {
for(auto start = v.begin(); start != v.end(); start++) {
(*start).drop();
}
}
}
}