My current understanding is that both the C++11 Move and Copy Assignment Operators should call delete to prevent memory leaks, but that the C++11 Move and Copy Constructors should not.
If my understanding is correct, the constructors do not need to call delete
, however I am unsure of why. Consider the following example:
class my_class
{
my_class(int num_data) : my_data{new double[num_data]}, my_data_length{num_data}
{
}
// This class manages a resource
double *my_data;
int my_data_length;
}
// Big 4 go here - see wikipedia example below.
my_class a(10);
my_class b(10);
my_class c(10);
a = b; // Need to delete old storage contained in a before reallocating
a(c); // For some reason we don't need to delete the old storage here? I find this puzzling
Looking at the example code from this wikipedia article, it is clear to me that:
The Move Constructor does not leak because the resources are transferred. Any allocated data which was pointed to by the class which is not expiring is transferred to the expiring class, and deleted by the destructor of the expiring class.
I am confused as to whether the Copy Constructor leaks, however.
The Move Assignment Operator presumably doesn't leak because it just swaps pointers over.
I am again confused by the Copy Assignment Operator. I am not sure why the use of a temporary object is required? My guess is the the resources owned by
tmp
andother
are destroyed when they go out of scope at the end of this function? (Except thattmp
has its resources swapped with the pointer in thethis
class?)
The code from this is provided below for convenience:
#include <cstring>
#include <iostream>
class Foo
{
public:
/** Default constructor */
Foo() :
data (new char[14])
{
std::strcpy(data, "Hello, World!");
}
/** Copy constructor */
Foo (const Foo& other) :
data (new char[std::strlen (other.data) + 1])
{
std::strcpy(data, other.data);
}
/** Move constructor */
Foo (Foo&& other) noexcept : /* noexcept needed to enable optimizations in containers */
data(other.data)
{
other.data = nullptr;
}
/** Destructor */
~Foo() noexcept /* explicitly specified destructors should be annotated noexcept as best-practice */
{
delete[] data;
}
/** Copy assignment operator */
Foo& operator= (const Foo& other)
{
Foo tmp(other); // re-use copy-constructor
*this = std::move(tmp); // re-use move-assignment
return *this;
}
/** Move assignment operator */
Foo& operator= (Foo&& other) noexcept
{
// simplified move-constructor that also protects against move-to-self.
std::swap(data, other.data); // repeat for all elements
return *this;
}
private:
friend std::ostream& operator<< (std::ostream& os, const Foo& foo)
{
os << foo.data;
return os;
}
char* data;
};
int main()
{
const Foo foo;
std::cout << foo << std::endl;
return 0;
}
I guess this is hints at why it is important to set (uninitialized/unallocated) dangling pointers to nullptr
, as this will prevent memory faults in a destructor when deleting it?
The reason I think this is because of the case where resources are transferred via a move constructor, but the expiring object receives a dangling pointer which was never allocated - we don't want the destructor to then be called and delete
the pointer - unless we ensure it points to nullptr
(no-op).
Can anyone clarify some of the points I have raised?
The move constructor and assignment operator are setting the data pointer to
nullptr
as the ownership of the pointer is being moved. If they didn't and delete was called on the original and the new one, you would have a double delete. Or if the old one was deleted, the new one would have an invalid pointer. Meanwhile the copy constructor and assignment operator are creating an entirely separate copy of the data.delete
onnullptr
is safe and does nothing as you have noted.Yes, the Move Constructor does not leak because the resources are transferred. More importantly, it never allocates it's own resources.
The copy constructor leaves the original intact, and makes a deep copy of the data. So both objects will clean up their own data.
Correct.
The copy assignment first makes a new copy using the copy constructor and then uses the move assignment operator to move the new copy into the object, and the old data into the temporary. Then the temporary goes out of scope, calling the destructor and deleting the old data.
Correct.