9

Suppose I have class like this one:

struct A{
  std::string a;
  std::string b;
  std::string c;
  std::string d;
};

If I use std::swap, it probably will do something like this:

// pseudo-code:
void std::swap(A &a, A &b){
   A tmp = std::move(a);
   a = std::move(b);
   b = std::move(tmp);
}

It will construct "empty" object tmp using default c-tor - generally cheap operation. Then it hopefully move 3 times, except in crazy cases when move decay to copy.

However if i do my own swap:

void swap(A &a, A &b){
   std::swap(a.a, b.a);
   std::swap(a.b, b.b);
   std::swap(a.c, b.c);
   std::swap(a.d, b.d);
}

It definitely will use less memory, but it still need to construct empty std::string - 4 times !!!

I could go wild, and make it with single std::string.

In all cases it does not look like big improvement.

Only proper case I could think of is if default c-tor is ridiculously expensive. Am I correct?

Nick
  • 9,962
  • 4
  • 42
  • 80

3 Answers3

2

Well you can't say that you need to, or never should, make your own swap, it all depends on context. In your example, it is most likely unnecessary, but your class' logic might be more complex.

Let's say you have a class that holds a pointer to some data structure, and a lot of metrics associated with that structure, each of them takes a long time to compute, and requires a lot of space to store, and these metrics are used as temporaries when doing some computations over the data (I know the following example is probably a crappy designed class, but it should illustrate the idea):

class MyClass
{
public:
   void doSomeWork()
   {
       //uses _metricsOneValue and _metricsTwoValue as temporaries,
       //calls other functions that use them as temporaries too, etc.
   }

private:
   //used only in doSomeWork and functions called by it.
   //Value overwritten each call.
   SomeBigClass _metricsOneValue; 
   SomeBigClass _metricsTwoValue;
   <...>
   SomeOtherClass * _data;
}

Now imagine you need to swap() two instances of this class. The most straightforward implementation will copy everything including the old metrics, which, in reality, are not needed at the moment, since they will be overwritten the next time you call doSomeWork() anyway. So in this case you can optimize the swap by just swapping the pointer to the data structure.

SingerOfTheFall
  • 29,228
  • 8
  • 68
  • 105
2

I would expect a sane std::swap(std::string&, std::string&) implementation to swap without any temporaries. It should just be able to exchange pointers, though I concede that the Small String Optimisation may throw a spanner in those particular works on some implementations.

You could always use the std::string::swap member function, which I'd expect even more to make use of such a delightful "optimisation". I mean, otherwise, what's the point of it? :)

void swap(A &a, A &b)
{
   a.a.swap(b.a);
   a.b.swap(b.b);
   a.c.swap(b.c);
   a.d.swap(b.d);
}

You're going to need to implement this anyway, even if it's just part of your move constructor, otherwise the default std::swap(A&, A&) implementation can't do much of anything.

In conclusion, yes you should write your own swap function, and yes you should use it to invoke the standard swap functionality. Because you need both, there's no performance comparison to draw.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
0

One case where there is a benefit to implement your own swap1 function is in an assignment operator to manage/transfer dynamically allocated memory, by using the copy and swap idiom.

If you have the following class:

#include <algorithm> // std::copy
#include <cstddef> // std::size_t

class MyArray{
public:
    // (default) constructor
   MyArray(std::size_t size = 0) 
       : mSize(size), mArray(mSize ? new int[mSize]() : 0)
   { }
   // copy-constructor
   MyArray(const MyArray& other) 
       : mSize(other.mSize), mArray(mSize ? new int[mSize] : 0),
   { std::copy(other.mArray, other.mArray + mSize, mArray); }
   // destructor
   ~MyArray(){ delete [] mArray; }

private:
    std::size_t mSize;
    int* mArray;
};

1. To implement the assignment operator

instead of:

MyArray& operator=(const MyArray& other){
   if (this != &other){
    // get the new data ready before we replace the old
    std::size_t newSize = other.mSize;
    int* newArray = newSize ? new int[newSize]() : 0;  
    std::copy(other.mArray, other.mArray + newSize, newArray);  
    // replace the old data  
    delete [] mArray;
    mSize = newSize;
    mArray = newArray;
}

return *this;
} 

you could do:

MyArray& operator=(MyArray other){
    swap(*this, other);  
    return *this;
} 

2. To swap the members of a class safely:

friend void swap(MyArray& first, MyArray& second){
    using std::swap; 
    // by swapping the members of two classes,
    // the two classes are effectively swapped
    swap(first.mSize, second.mSize); 
    swap(first.mArray, second.mArray);
}

Note: insight in this answer borrowed from this.


1 A swap function is a non-throwing function that swaps two objects of a class, member for member. We might be tempted to use std::swap instead of providing our own, but this would be impossible; std::swap uses the copy-constructor and copy-assignment operator within its implementation, and we'd ultimately be trying to define the assignment operator in terms of itself!

Community
  • 1
  • 1
Ziezi
  • 6,375
  • 3
  • 39
  • 49