samedi 28 avril 2018

Feasibility of a user-defined swap method

Thinking about this topic I got a thought (given that I'm talking about C++11) that a user-defined swap should be provided only for types which don't provide a move semantic, more precisely a move assignment operator ('cause I think it's more common to have a move ctr while lack a move assignment operator); or the move ctor and/or the move assignment operator have some side-effects which ain't desired to happen.

Let's see the examples: [ the same as in a linked topic ]

class remote_connection
{
public:
  int socket_fd;

  friend void swap( remote_connection& lhs, remote_connection& rhs ) = delete;
}

As we have NO provided a user-defined swap the code trying to swap objects of a remote_connection type calls a swap instantiated from std::swap which causes a move construction and two move assignments to happen (as @1201ProgramAlarm indicated).

So, to swap objects a compiler issues: one function call to a std::swap, one call to a move ctr and two calls to a move assign operator. Which leads to 3 int copies to make an actual swap.

As a result: 4 function calls and 3 copies.

Let's implement a swap method:

class remote_connection
{
public:
  int socket_fd;

  friend void swap( remote_connection& lhs, remote_connection& rhs )
  {
    using std::swap;

    swap( lhs.socket_fd, rhs.socket_fd );  
  } 
}

As we have provided a user-defined swap the code trying to swap objects of a remote_connection type calls this user-defined swap which causes 3 copies for int to happen (create a tmp from a rhs, copy a lhs to the rhs, copy the tmp to the lhs).

So, to swap objects a compiler issues: one function call to a swap (found by ADL), one call to a std::swap(int&, int&). Which also leads to 3 int copies to make an actual swap.

As a result: 2 function calls and 3 copies.

The user-defined swap won, but what if we add some members to our class:

class remote_connection
{
public:
  int socket_fd;
  int a, b, c, d, e;

  friend void swap( remote_connection& lhs, remote_connection& rhs )
  {
    using std::swap;

    swap( lhs.socket_fd, rhs.socket_fd );  
    swap( lhs.a, rhs.a );  
    swap( lhs.b, rhs.b );  
    swap( lhs.c, rhs.c );  
    swap( lhs.d, rhs.d );  
    swap( lhs.e, rhs.e );  
  } 
}

Here we have: 8 function calls and 6 copies. But if we didn't provide the user-defined swap, we'd have: 4 function calls and 6 copies.

So, it seems that whatever class we have (derived from a class with plenty members, consisting of plenty sub-objects of built-in and user-defined types) it's more sane, IMHO, to not provide a user-defined swap. Not only this, but also such the swap is hard to support (if we changed something in a class and forgot to reflect that changes in the swap we get a surprise.)

So the questions is: "If we have no side-effects in move ctr/move assign operator and provide them both, do we have to implement a user-defined swap, or is it a good practice to rely on stl provided one in such circumstances?"

Sorry for such ammount of so, I'm not a native speaker :)

P.S. I forgot to add that we need, IMHO, a user-defined swap if we're going to implement a copy-and-swap idiom and we've implemented a move assignment operator.

Aucun commentaire:

Enregistrer un commentaire