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?
Aucun commentaire:
Enregistrer un commentaire