c++rule-of-threerule-of-five

Reasons to have special copy assignment operator instead of simple destructor and in-place constructor


I have a class with own resource management:

class Lol
{

private:
  // This is data which this class allocates
  char *mName = nullptr;

public:
    Lol(std::string str) // In constructor just copy data from string
    {
        auto cstr = str.c_str();
        auto len = strlen(cstr);
        mName = new char[len + 1];
        strcpy(mName, cstr);
    };

    ~Lol() // And of course clean up
    {
        delete[] mName;
    }
}

I implemented copy constructor which just copies managed data:

    Lol(const Lol &other)
    {
        auto cstr = other.mName;
        auto len = strlen(cstr);
        mName = new char[len + 1];
        strcpy(mName, cstr);
    };

I also need to implement copy assignment operator. I just did this:

    Lol &operator=(const Lol &other)
    {
        if (this == &other)
        {
            return *this;
        }

        // Clean up my resources
        this->~Lol();
 
        // And copy resources from "other" using already implemented copy constructor
        new (this) Lol(other);
    }

Looks like this copy assignment operator will work for all classes. Why do I need to have another code in the copy assignment operator? What is use case for it?


Solution

  • Use the copy and swap Idiom.

    Lol &operator=(const Lol &other)
    {
        if (this == &other)
        {
            return *this;
        }
    
        // Clean up my resources
        this->~Lol();
    
        // Copy can throw.
        // Then your object is in an undefined state.
        new (this) Lol(other);
    
        // You forgot the return:
        return *this;
    }
    

    So this does not provide the strong (or any) exception gurantees.

    The preferred way would be:

    Lol& operator=(Lol const& other)
    {
        Lol   copy(other);         // Here we use the copy constructor
                                   // And the destructor at the end of
                                   // function cleans up the scope
                                   // Note this happens after the swap
                                   // so you are cleaning up what was in
                                   // this object.
        swap(copy);
        return *this;
    }
    void swap(Lol& other) noexcept
    {
        std::swap(mName, other.mName);
    }
    

    Nowadays we have improved on this original

    Lol& operator=(Lol copy)      // Notice we have moved the copy here.
    {
        swap(copy);
        return *this;
    }
    

    The exciting thing here is that this version of assignment works for both copy and move assignment just as effeciently.