I've seen some smart pointers implementing operator=
in two ways:
A) One that assigns the raw pointer to the other raw pointer:
SmartPointer& operator=(const SmartPointer& rhs)
{
delete m_ptr;
m_ptr = rhs.m_ptr;
return *this;
}
B) And one that nullifies the right hand side's pointer after the assignment:
SmartPointer& operator=(SmartPointer& rhs)
{
delete m_ptr;
m_ptr = rhs.m_ptr;
rhs.m_ptr = nullptr
return *this;
}
My question would be which one is more advised to be used? My problem with B) is that if one may want to further operate on the second smart pointer (see code below), the program would crash (if not checking for null pointer) or do nothing. And that doesn't seem too nice :)
SmartPointer<MyClass> p1(new MyClass());
SmartPointer<MyClass> p2(new MyClass());
p1 = p2;
p2->someMethod(); // <----- BOOM!
If you want your smart-pointer to be copyable, declaration (A) is fine; just remember that you cannot deallocate storage twice, meaning that there has to be some way to show that the copied smart pointer isn't really owning the resource it refers to.
Declaration (B) is however faulty since it doesn't follow any semantics that are within the language; it's weird that the right-hand-side, which lives on beyond the operation, gets modified when it acts as a mere source to the assignment.
If you plan to move data from one side to the other you should use an overload that accepts an rvalue reference. Said reference can only bind to a temporary or something which has explicitly been stated to act like one (ie. something which the developer knows might have an undetermined value after the operation).
rvalue references was introduced in C++11, and an implementation might look like the below.
SmartPointer& operator=(SmartPointer&& rhs) // (B), move assign
{
delete m_ptr; // release currently held resource
m_ptr = rhs.m_ptr; // assign new resource
rhs.m_ptr = nullptr; // prevent `rhs` from deleting our memory, it's no longer in charge
return *this;
}
SmartPointer<MyClass> p1(new MyClass());
SmartPointer<MyClass> p2(new MyClass());
p1 = p2; // ill-formed, (B) is not applicable; cannot bind lvalue to rvalue reference
p1 = std::move (p2) // legal
In the C++11 library we std::unique_ptr
, std::shared_ptr
, and std::weak_ptr
.
Looking at their implementation should serve as a great understanding of how smart pointers are made to work, and how the differences in semantics determine the differences in the code written.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With