c++copy-constructordeep-copyassignment-operator

Copy semantics and vectors


I am dealing with objects that allocate memory for internal use. Currently, they are not copyable.

E.g.

class MyClass
{
public:
    MyClass() { Store = new int; }
    ~MyClass() { delete Store; }

private:
    int* Store;
};

I would like to add what it takes to allow assignment, both with copy and move semantics, and also be able to store them in vectors (with move semantics). I don't want to use smart pointers, and want to keep it simple.

What class members should I define ? How can I force copy assignment or move assignment ? Which copy will be performed when passing the objects by value or by reference ? Will the implementation differ across the successive versions of C++ ?

E.g.

MyClass A, B;
A = B; // How to force copy or move ?
std::vector<MyClass> V = { A, B };

Solution

  • You asked about vectors, so... here is class tbx::Vector. I wrote it as an exercise. Its design follows the talk given by Arthur O'Dwyer at CppCon 2019. See the YouTube video entitled Back to Basics: RAII and the Rule of Zero.

    To focus more closely on the copy semantics you ask about, I have included just the five "special" member functions, plus a couple of other ctors and the member functions reserve and swap. Although class Vector implements almost all of the interface of std::vector, the other functions do not appear here.

    I've left hooks where you can insert whatever code is needed in the event that an allocation attempted by operator new fails.

    This is not intended as production code. First, why reinvent the wheel? Second, there are many optimizations that have been omitted, Third, some functionality has been omitted, for example, I did not provide the full complement of ctors or support for allocators.

    template< typename T >
    class Vector
    {
    public:
        using value_type = T;
        using size_type = std::size_t;
        using iterator = value_type*;
        using const_iterator = value_type const*;
    
    private:
        value_type* data_{ nullptr };
        size_type capacity_{};
        size_type size_{};
        enum : size_type { zero, one };
    
    public:
        Vector() noexcept
            = default;
    
        explicit Vector(size_type const size)
            : data_{ nullptr }, capacity_{ size }, size_{ size }
        {
            if (zero < size)
            {
                try { data_ = new value_type[size]; }
                catch (std::bad_alloc const&) { throw; }
            }
        }
    
        Vector (Vector const& that)
            : data_{ nullptr }
            , capacity_{ that.size_ }  // "shrink to fit"
            , size_{ that.size_ }
        {
            if (that.size_ != zero)
            {
                try { data_ = new value_type[that.size_]; }
                catch (std::bad_alloc const&) { throw; }
                std::copy(that.data_, that.data_ + size_, data_);
            }
        }
    
        Vector (Vector&& that) noexcept
            : data_     { std::exchange(that.data_, nullptr)  }
            , capacity_ { std::exchange(that.capacity_, zero) }
            , size_     { std::exchange(that.size_, zero)     }
        {}
    
        ~Vector() {
            delete[] data_;
        }
    
        Vector& operator=(Vector that) noexcept {
            swap(that);
            return *this;
        }
    
        void reserve(size_type const capacity) {
            if (capacity_ < capacity) {
                value_type* p{ nullptr };
                try { p = new value_type[capacity]; }
                catch (std::bad_alloc const&) { throw; }
                if (data_ != nullptr)
                    std::copy(data_, data_ + size_, p);
                capacity_ = capacity;
                std::swap(data_, p);
                delete[] p;
            }
        }
    
        void swap(Vector& that) noexcept {
            using std::swap;
            swap(capacity_, that.capacity_);
            swap(size_, that.size_);
            swap(data_, that.data_);
        }
    
        friend void swap(Vector& a, Vector& b) noexcept {
            a.swap(b);
        }
    };