6

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!
Iosif Murariu
  • 2,019
  • 1
  • 21
  • 27
  • 1
    Smart pointers have a few flavours. E.g. `unique_ptr`, `weak_pointer` `shared_ptr`. What one are you referring to? – Ed Heal Jun 14 '14 at 10:41
  • 3
    second one is like `auto_ptr` which is deprecated. and don't re-implement them without good reason. (your one can't handle `p1 = p1`) – Bryan Chen Jun 14 '14 at 10:41
  • @EdHeal my knowledge about smart pointers is quite limited and was now googling and "stackoverflowing" for how they're implemented. Will look up the types you mentioned :) – Iosif Murariu Jun 14 '14 at 10:45
  • @BryanChen so I guess B) is out of the question :) – Iosif Murariu Jun 14 '14 at 10:45
  • 1
    If you do `p1 = p1` both versions will do the wrong thing. – nwp Jun 14 '14 at 11:11

1 Answers1

6

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.

Community
  • 1
  • 1
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • I believe this, along with the knowledge of the existance of unique_ptr, shared_ptr and weak_ptr, is the right answer for what OP wants to do. Anything he wants to do. – Marco A. Jun 14 '14 at 10:56