Hello. I recently started learning about smart pointers and such, mutexes and what-not. I wanted to make an int array class that holds a dynamically allocated array and has a destructor that will call delete on the array when called. I decided to use smart pointers instead, but I don't know how to design the move semantics or copy semantics. In my last question I asked how to do this with a raw pointer. Sorry, I don't have any code to build off of this time.
So, when an Array object is passed to the assignment operator, it first goes to the copy constructor (correct?), which copies the individual elements in o.m_data to this->m_data and then it goes to the assignment operator, which swaps its copied data with this and then destroys the copy? This seems to make sense, but I'm not sure.
Also, isn't line 10 evil? Since it calls T's constructor with new, rather than std::make_unique? After all, T's constructor may throw an exception.
The assignment operator works for both copy and move assignment by using the copy and move constructors on the parameter other. The argument will either be copied or moved to other; then it's contents are swapped with this; then the destructor will be called on other.
Oh, right. Thanks, I noticed though, operator= assumes that T has member values m_data and m_size. Did you mean Array<T>& operator=(Array<T> other) noexcept?
EDIT: It also says that *this is of type T, which it can't be.
You should also have move assignment as well as copy assignment. This will mean a change to copy assignment as you can't have function overloads with a param passed by value and by refref.
But unless I'm still recovering from Wednesday (England win! - possible..), surely for L24 other is passed by value which involves a copy in all cases.....
> Why does the OP need a move ctor? Doesn't unique_ptr implement it ?
It is not really needed; the class itself is not really needed when we have std::vector
I think SirEnder125's intention was to learn about implementing the foundation operations in a class.
> You can't pass by value in a constructor as you're then got infinite recursion.
This was your original assertion, on which I commented:
'You should also have move assignment as well as copy assignment.'
That was about the assignment operator, not the constructor.
I pointed out that 'There is nothing wrong in using the copy and swap idiom to implement a unifying assignment operator.' Assignment operator, not constructor.
L24. other is passed by value. So other is created via a call to the copy constructor. The copy constructor can throw, so operator=() shouldn't be marked noexcept.
Whether the passed arg is an l-value or a r-value, a copy is still undertaken. So move semantics are not being implemented for assignment.
In order to have move semantics for assignment, we need the two versions for l-value and r-value.
> Whether the passed arg is an l-value or a r-value, a copy is still undertaken.
> So move semantics are not being implemented for assignment.
For lvalue arguments, the copy constructor is invoked (a copy is made).
For rvalue arguments, the move constructor is invoked (the object is moved).
Move semantics are used for rvalues, and copy is used for lvalues
That is why it is referred to as a unifying assignment operator.
I had posted a link in my very first post in this thread; consider reading it.
OK. I haven't done it that way before - I've always used 2 functions. Never too late to learn! But in this case of the unifying assignment, shouldn't it not be marked noexcept as in the case of a lvalue, the copy constructor called can throw? The link doesn't show as noexcept.
> shouldn't it not be marked noexcept as in the case of a lvalue, the copy constructor called can throw?
With a non-throwing swap, I would mark it as noexcept
noexcept is primarily used to indicate to the compiler that the function would not throw exceptions; which is the case for the unifying assignment operator with a non-throwing swap. The copy constructor may throw; but that throw would take place at the call site, in the context of the caller, before the unifying assignment operator is entered into.
IS:
When a function is called, each parameter is initialized with its corresponding argument. ...
The initialization and destruction of each parameter occurs within the context of the calling function. http://eel.is/c++draft/expr.call#7