Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Smart Pointer operator=

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!
like image 271
Iosif Murariu Avatar asked Jun 14 '14 10:06

Iosif Murariu


1 Answers

Introduction

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.


Danger, DANGER!

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

What's in the standard?

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.

like image 180
Filip Roséen - refp Avatar answered Sep 28 '22 10:09

Filip Roséen - refp