c++c++20raiistdoptional

Using std::optional to invalidate my RAII object in move constructor/asignment


Say I have an RAII class, instances of which should never be copied:

class Session {

public:
    Session(); // Allocates a resource and sets generates a unique Session::id. 
    ~Session(); // Frees the resource

    Session(const Session&) = delete;
    Session& operator = (Session&) = delete;

private:
  std::uint32_t id;
}

Given that I can't allow copies, I'd like to allow moving, so I need to implement the move constructor and move-assignment operator but I'm not sure what I should do with Session::id of the moved instance.

I can either:

  1. Set it to some value known to be invalid (maybe change the type to a signed int and use -1 as the invalid value)
  2. Use something like std::optional and set it to std::nullopt to invalidate it.

Which (if either) of those options is correct?


Solution

  • Since this can be done without std::optional by just sacrificing one value out of the 232 possible ids, I'd do like std::string::npos and initialize a constant of the unsigned type your id has with -1.

    Example:

    class Session {
    public:
        // the constant used to signal an invalid id:
        static constexpr std::uint32_t invalid_id = static_cast<std::uint32_t>(-1);
    
        Session() :
            id(id_counter++)
            /* allocate the resource */
        {}
        Session(const Session&) = delete;
        Session(Session&& other) noexcept :
            id(std::exchange(other.id, invalid_id)) // exchange with the invalid id
            /* move or exchange the resource */
        {}
        Session& operator=(const Session&) = delete;
        Session& operator=(Session&& other) noexcept {
            // check for self-assignment if needed
            std::swap(id, other.id);
            // swap the resource
            return *this;
        }
        ~Session() {
            if(id != invalid_id) { // if the check is even needed
                /* free the resource */
            }
        }
    
    private:
        inline static std::uint32_t id_counter = 0;
        std::uint32_t id;
    };
    

    Demo

    An option would be to let 0 be the invalid_id if that would feel more natural in your case. You'd just have to initialize invalid_id with 0 and change the id counting to id(++id_counter).