c++raii

How can I recreate an RAII object without briefly having two?


class MyObject
{
public:
    MyObject(fs::path& filename) { ... }
private:
    ...; // [edit] removed poor example with std::vector
};

int main()
{
    MyObject myObject("1_gb_file.dat");

    // Actually I want something else
    myObject = MyObject("2_gb_file.dat");  // <- at one point I have 3gb of file loaded unnecessarily!
}

How can I replace myObject without ever having both allocated at once?

In some ways I'd like the operator=() to be able to happen first, with something a bit like return value optimization to construct the object in-place. A simple way to do what I want is the following, but it means I have a nullable object. I want myObject to be guaranteed always valid but I also want to be able to replace it.

int main()
{
    std::optional<MyObject> myObject("1_gb_file.dat");

    // Better, but not atomic
    myObject.reset();
    myObject = MyObject("2_gb_file.dat");

    // Better, but myObject.reset() still exists and optional holds an extra bool
    myObject.emplace("2_gb_file.dat");
}

Solution

  • Assuming a move assignment operator exists,

    MyObject myObject("1_gb_file.dat");
    
    // Actually I want something else
    {
        auto tmp = std::move(myObject);
        // destroys myObject
    }
    myObject = MyObject("2_gb_file.dat");
    

    Shorter version (ty, @o11c):

    // destroy myObject
    MyObject(std::move(myObject));
    

    16.4.6.15 Moved-from state of library types

    Objects of types defined in the C++ standard library may be moved from (11.4.5.3). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

    3.67 valid but unspecified state

    〈library〉 value of an object that is not specified except that the object’s invariants are met and operations on the object behave as specified for its type

    Example 1: If an object x of type std::vector<int> is in a valid but unspecified state, x.empty() can be called unconditionally, and x.front() can be called only if x.empty() returns false.

    Somewhat related, Jon Kalb makes some good points about how being able to call x.empty() is actually a bad thing for performance, because moving must waste time altering size and capacity. Even worse for std::list using sentinel nodes:

    https://www.youtube.com/watch?v=fcRHiFH04a4


    Note that without actually invoking MyObject(MyObject&&) or MyObject& operator=(MyObject&&), no move actually happens. For example std::ignore = std::move(myObject) does nothing. std::move() is just a cast.