Here is an exercise from C++ Primer 5th Edition:
Exercise 13.53:
As a matter of low-level efficiency, the HasPtr
assignment operator is not ideal. Explain why. Implement a
copy-assignment and move-assignment operator for HasPtr and compare
the operations executed in your new move-assignment operator versus
the copy-and-swap version.(P.544)
File hasptr.h
:
//! a class holding a std::string*
class HasPtr
{
friend void swap(HasPtr&, HasPtr&);
friend bool operator <(const HasPtr& lhs, const HasPtr& rhs);
public:
//! default constructor.
HasPtr(const std::string &s = std::string()):
ps(new std::string(s)), i(0)
{ }
//! copy constructor.
HasPtr(const HasPtr& hp) :
ps(new std::string(*hp.ps)), i(hp.i)
{ }
//! move constructor.
HasPtr(HasPtr&& hp) noexcept :
ps(hp.ps), i(hp.i)
{ hp.ps = nullptr; }
//! assignment operator
HasPtr&
operator = (HasPtr rhs);
//! destructor.
~HasPtr()
{
delete ps;
}
private:
std::string *ps;
int i;
};
A part of the file hasptr.cpp
:
//! specific swap.
inline void
swap(HasPtr &lhs, HasPtr &rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
swap(lhs.i, rhs.i); // swap the int members
std::cout <<"swapping!\n";
}
//! operator = using specific swap
HasPtr&
HasPtr::operator = (HasPtr rhs)
{
swap(*this,rhs);
return *this;
}
My question is why it is not efficient to do so?
Step 1
Set up a performance test which exercises the move assignment operator.
Set up another performance test which exercises the copy assignment operator.
Step 2
Set up the assignment operator both ways as instructed in the problem statement.
Step 3
Iterate on Steps 1 and 2 until you have confidence that you did them correctly.
Step 3 should help educate you as to what is going on, most likely by telling you where the performance is changing and where it is not changing.
Guessing is not an option for Steps 1-3. You actually have to do them. Otherwise you will (rightly) have no confidence that your guesses are correct.
Step 4
Now you can start guessing. Some people will call this "forming a hypothesis." Fancy way of saying "guessing." But at least now it is educated guessing.
I ran through this exercise while answering this question and noted no significant performance difference on one test, and a 6X performance difference on the other. This further led me to an hypothesis. After you do this work, if you are unsure of your hypothesis, update your question with your code, results, and subsequent questions.
Clarification
There are two special member assignment operators which typically have the signatures:
HasPtr& operator=(const HasPtr& rhs); // copy assignment operator
HasPtr& operator=(HasPtr&& rhs); // move assignment operator
It is possible to implement both move assignment and copy assignment with a single assignment operator with what is called the copy/swap idiom:
HasPtr& operator=(HasPtr rhs);
This single assignment operator can not be overloaded with the first set.
Is it better to implement two assignment operators (copy and move), or just one, using the copy/swap idiom? This is what Exercise 13.53 is asking. To answer, you must try both ways, and measure both copy assignment and move assignment. And smart, well meaning people get this wrong by guessing, instead of testing/measuring. You have picked a good exercise to study.
As the problem suggests, it's "a matter of low-level efficiency". When you use HasPtr& operator=(HasPtr rhs)
and you write something like hp = std::move(hp2);
, ps
member is copied twice (the pointer itself not the object to which it points): Once from hp2
to rhs
as a result of calling move constructor, and once from rhs
to *this
as a result of calling swap
. But when you use HasPtr& operator=(HasPtr&& rhs)
, ps
is copied just once from rhs
to *this
.