1

While trying to implement MyVector I end up with:

#include <iostream>
#include <string>

using namespace std;

template <typename T>
class MyVector
{
    int m_size = 0;
    int m_capacity = 0;

    T* m_data = nullptr;

public:
    MyVector()
    {
        cout << "defautl ctor" << endl;

        realloc(2);
    }

    MyVector(const MyVector& v)
    : m_size(v.m_size), 
      m_capacity(v.m_capacity)
    {
        cout << "copy ctor" << endl;

        m_data = new T[m_size];
        *m_data = *v.m_data;
    }

    MyVector(MyVector&& v)
    : m_size(v.m_size), 
      m_capacity(v.m_capacity)
    {
        cout << "move ctor" << endl;

        m_data = move(v.m_data);

        v.m_data = nullptr;
        v.m_size = 0;
        v.m_capacity = 0;
    }

    MyVector& operator= (const MyVector& v)
    {
        cout << "copy assignment operator" << endl;

        // m_size = v.m_size;
        // m_capacity = v.m_capacity;
        // *m_data = *v.m_data;

        MyVector<int> copy = v;
        swap(*this, copy);

        return *this;
    }

    void push_back(const T& value)
    {

        if (!(m_size < m_capacity))
        {
            // cout << value << " size is " << m_size << " capacity is " << m_capacity << endl;

            realloc(m_size*2);
            // return;
        }

        m_data[m_size++] = value;
    }

    T& operator[] (int index)
    {
        cout << "index " << index << " of size " << m_size << endl;

        if (!(index < m_size))
            cout << "index out of bounds" << endl;

        return m_data[index];
    }

    int size()
    {
        return m_size;
    }

    T* begin()
    {
        return &m_data[0];
    }

    T* end()
    {
        return &m_data[size()];
    }

private:
    void realloc(int new_capacity)
    {
        // cout << __func__ << " new capacity " << new_capacity << endl;

        T* data = new T[new_capacity];

        for (int i = 0; i < m_size; i++)
            data[i] = m_data[i];

        delete[] m_data;
        m_data = data;

        m_capacity = new_capacity;
    }
};


int main(int argc, char** argv)
{
    cout << "starting..." << endl;

    MyVector<int> a;
    a.push_back(7);

    MyVector<int> d;
    d = a; 

    cout << a[0] << endl;
    cout << d[0] << endl;

    return 0;
}

Where the operator= is

    MyVector& operator= (const MyVector& v)
    {
        cout << "copy assignment operator" << endl;

        // m_size = v.m_size;
        // m_capacity = v.m_capacity;
        // *m_data = *v.m_data;

        MyVector<int> copy = v; // copy and swap
        swap(*this, copy);

        return *this;
    }

However this led to what seems to be a recursive behavior. So, is my understanding of the copy and swap approach wrong? Or is there something else I'm missing?

KcFnMi
  • 5,516
  • 10
  • 62
  • 136
  • Where did you define your `swap` function for `MyVector`? – Eljay Oct 06 '22 at 15:56
  • That's `std::swap`, isn't? Should I write my own swap? – KcFnMi Oct 06 '22 at 15:57
  • FYI: `*m_data = *v.m_data;` -- That looks highly suspicious. This does not do a copy of the data from one dynamic array to the other. I would expect a call to `std::copy` with the appropriate arguments. – PaulMcKenzie Oct 06 '22 at 16:01
  • 1
    `std::swap` uses `MyVector::operator=`, thus the recursion. Is it surprising you? – 273K Oct 06 '22 at 16:02
  • 2
    If you don't write your own swap, you'll get the fallback `std::swap` which will do the `MyVector temp = a; a = b; b = a;`. Which is recursive, given your `operator=`. – Eljay Oct 06 '22 at 16:02
  • 1
    FYI: `MyVector& operator= (const MyVector& v)` could be: `MyVector& operator= (MyVector v)`. With that, your code doesn't need to manually create the copy here `MyVector copy = v; // copy and swap`, plus the compiler has a better chance of optimizing the code. – PaulMcKenzie Oct 06 '22 at 16:06
  • Does this answer your question? [What is the copy-and-swap idiom?](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom) – John Bollinger Oct 06 '22 at 16:07
  • Note well that the answers to the dupe target explicitly discuss how you must write your own swap for copy & swap, not use `std::swap`. – John Bollinger Oct 06 '22 at 16:08
  • @PaulMcKenzie, appreciate if you could expand on that. If `code doesn't need to manually create the copy` what happens instead? – KcFnMi Oct 06 '22 at 16:35
  • @KcFnMi -- Since the parameter would be passed by-value, the compiler will automatically make the copy. Since the compiler will be making the copy, the compiler knows how to optimize the "copy making". By you stepping in and manually creating the copy, you are potentially stopping the compiler from optimizing your code right there. – PaulMcKenzie Oct 06 '22 at 17:43

1 Answers1

2

As stated in comments, you did not implement a swap() function for MyVector, so the statement swap(*this, copy); is calling std::swap() (one of the many pitfalls of using using namespace std;), which will invoke your operator= again, hence the recursive behavior you are seeing.

Also, your copy constructor is not implemented correctly. It is not copying all of the elements from the input array into the new array. It is copying only the 1st element.

Also, you are missing a destructor to free the allocated array.

Also, since MyVector has both copy and move constructors, your two assignment operator='s can (and should) be merged into one operator that takes a MyVector by value. This lets the compiler decide whether to call the operator with copy or move semantics depending on whether the caller is passing in an lvalue or an rvalue, respectively. The operator can then just swap whatever it is given, as the input will have already been copied or moved before the operator is entered.

Try something more like this:

#include <iostream>
#include <string>
#include <algorithm>
#include <stdexcept>

template <typename T>
class MyVector
{
    int m_size = 0;
    int m_capacity = 0;
    T* m_data = nullptr;

public:
    MyVector()
    {
        std::cout << "default ctor" << std::endl;

        realloc(2);
    }

    MyVector(const MyVector& v)
    {
        std::cout << "copy ctor" << std::endl;

        realloc(v.m_capacity);
        std::copy_n(v.m_data, v.m_size, m_data);
        m_size = v.m_size;
    }

    MyVector(MyVector&& v)
    {
        std::cout << "move ctor" << std::endl;

        v.swap(*this);
    }

    ~MyVector()
    {
        std::cout << "dtor" << std::endl;

        delete[] m_data;
    }

    MyVector& operator= (MyVector v)
    {
        std::cout << "assignment operator" << std::endl;

        v.swap(*this);

        return *this;
    }

    void push_back(const T& value)
    {
        if (m_size >= m_capacity)
        {
            // std::cout << value << " size is " << m_size << " capacity is " << m_capacity << std::endl;

            realloc(m_size * 2);
        }

        m_data[m_size] = value;
        ++m_size;
    }

    T& operator[] (int index)
    {
        std::cout << "index " << index << " of size " << m_size << std::endl;

        if (index < 0 || index >= m_size)
            throw std::out_of_range("index out of bounds");

        return m_data[index];
    }

    int size() const
    {
        return m_size;
    }

    T* begin()
    {
        return &m_data[0];
    }

    T* end()
    {
        return &m_data[m_size];
    }

    void swap(MyVector &other)
    {
        std::swap(m_data, other.m_data);
        std::swap(m_size, other.m_size);
        std::swap(m_capacity, other.m_capacity);
    }

private:
    void realloc(int new_capacity)
    {
        // std::cout << __func__ << " new capacity " << new_capacity << std::endl;

        T* data = new T[new_capacity];

        std::copy_n(m_data, m_size, data);
        std::swap(m_data, data);
        m_capacity = new_capacity;

        delete[] data;
    }
};

// for std::swap() to use via ADL...
void swap(MyVector &v1, MyVector &v2)
{
    v1.swap(v2);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Concerning the `operator=` merge, if I choose not to merge it then the alternative could be having `MyVector& operator= (const MyVecto&r v)` and `MyVector& operator= (MyVector&& v)` with same body? – KcFnMi Oct 07 '22 at 03:41
  • @KcFnMi they would have similar code, but not exactly the same code, eg: `MyVector& operator= (const MyVector &v) { if (&v != this) { MyVector temp(v); temp.swap(*this); } return *this; } MyVector& operator= (MyVector &&v) { MyVector temp(std::move(v)); temp.swap(*this); return *this; }` – Remy Lebeau Oct 07 '22 at 04:46