DEV Community

Aleksandar Milenkovic
Aleksandar Milenkovic

Posted on

C++ Manual Drop

Source Code

The source code of this library can be found at: https://github.com/BeAllAround/ManualDrop

Overview

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
  */
}
Enter fullscreen mode Exit fullscreen mode

Types with expensive moved-from destructors

Some libraries don't bother making moved-from destruction cheap.

For example, even after moving, we end up with:

~S() {
    unregister_from_global_registry();
}
Enter fullscreen mode Exit fullscreen mode

Installation

Just dump the ManualDrop.hpp header into your project and you are good to go!

Examples

Manual Destroy

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()
*/
Enter fullscreen mode Exit fullscreen mode

Moving it under RAII ownership

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()
*/
Enter fullscreen mode Exit fullscreen mode

Guaranteed No Automatic RAII Cleanup

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;
Enter fullscreen mode Exit fullscreen mode

Usage of Copy/Move Assignments

{
  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();
}
Enter fullscreen mode Exit fullscreen mode

Buffer Reallocation RAII Improvements

// 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();
      }

    }
  }

}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)