Skip to content

Instantly share code, notes, and snippets.

@thejohnfreeman
Last active April 5, 2021 16:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thejohnfreeman/c972a2e521e788386f37c14b9afe691d to your computer and use it in GitHub Desktop.
Save thejohnfreeman/c972a2e521e788386f37c14b9afe691d to your computer and use it in GitHub Desktop.

My last post covered the importance of supplying a RAII type for every pair of balanced functions in an API. Is it possible to write a reusable generic type that can implement that pattern for developers?

We might imagine a type that calls the start function in its constructor and the stop function in its destructor. The constructor would be parameterized by the start and stop functions; let them be callable objects in the general case. The start callable object would not be needed after the constructor returns, and honestly, maybe it shouldn't be passed to the constructor. Maybe the caller of the constructor should be responsible for calling the start function first, and then just constructing the gener

For this technique, we could write a generic type, call it Stopper, that calls an arbitrary function in its destructor. Our start function can return a Stopper, passing to it a lambda expression that calls the stop function. But how would we write the return type for such a start function? Such a Stopper type would have to be a template to call an arbitrary callable without introducing std::function and a virtual function call, but we won't know the type parameter for that template until inside the start method where we construct the closure. Instead, we can use return type deduction and a type alias:

#include <type_traits>
#include <utility>
#include <iostream>

template <typename Stop>
struct Stopper {
    Stop stop;
    bool started;
    Stopper(Stop&& stop) :
        stop(std::move(stop)), started(true) {}
    Stopper(Stopper const&) = delete;
    Stopper& operator=(Stopper const&) = delete;
    Stopper(Stopper&& rhs) :
        stop(std::move(rhs.stop)), started(rhs.started)
    {
        rhs.started = false;
    }
    Stopper& operator=(Stopper&& rhs) {
        stop = std::move(rhs.stop);
        started = rhs.started;
        rhs.started = false;
        return *this;
    }
    void operator() () {
        if (started) {
            started = false;
            stop();
        }
    }
    ~Stopper() {
        (*this)();
    }
};

[[nodiscard]] auto start() {
    return Stopper([](){ std::cout << "stopped!\n"; });
}
using Stop = std::invoke_result_t<decltype(start)>;

int main() {
    Stop stop1 = start();
    Stop stop2 = std::move(stop1);
    // Stop stop3 = stop2; // error: cannot copy
    return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment