samedi 26 novembre 2016

Moving data members of an object

Imagine that we got an object passed to a function and would like move semantics to be applied to its individual members. Which option to choose: call std::move on each member or move the whole object (several times) and then access its members as rvalues?

To be more specific, consider the following example. There is a downloader class which accepts connection parameters as an object. I want to move-assign its members to the members of the downloader, so I'm doing the following:

class ConnectionParameters
{
public:
    ConnectionParameters(string server, string login, string password, unsigned short port, unsigned int connectionLimit, unsigned long long speedLimit)
        : server(move(server))
        , login(move(login))
        , password(move(password))
        , port(port)
        , connectionLimit(connectionLimit)
        , speedLimit(speedLimit) {}

    ConnectionParameters(const ConnectionParameters &) = delete;    // disable copy constructor
    ConnectionParameters(ConnectionParameters &&) = default;    // default move constructor

    const string & getServer() const & { return this->server; }
    string && getServer() && { return move(this->server); }

    const string & getLogin() const & { return this->login; }
    string && getLogin() && { return move(this->login); }

    const string & getPassword() const & { return this->password; }
    string && getPassword() && { return move(this->password); }

    unsigned short getPort() const  { return this->port; }
    unsigned int getConnectionLimit() const { return this->connectionLimit; }
    unsigned long long getSpeedLimit() const { return this->speedLimit; }

private:
    string server, login, password;
    unsigned short port;
    unsigned int connectionLimit;
    unsigned long long speedLimit;
};

class Downloader
{
public:
    Downloader(ConnectionParameters connectionParameters, string downloadDirectory, unsigned long long diskSpaceMinimum)
        : server(move(connectionParameters).getServer())
        , login(move(connectionParameters).getLogin())
        , password(move(connectionParameters).getPassword())
        , port(connectionParameters.getPort())
        , connectionLimit(connectionParameters.getConnectionLimit())
        , speedLimit(connectionParameters.getSpeedLimit())
        , downloadDirectory(move(downloadDirectory))
        , diskSpaceMinimum(diskSpaceMinimum) {}

private:
    string server, login, password;
    unsigned short port;
    unsigned int connectionLimit;
    unsigned long long speedLimit;
    string downloadDirectory;
    unsigned long long diskSpaceMinimum;
};

What I don't like in this approach is that std::move is called several times on the same connectionParameters object. Although it works, it probably violates the principle of move semantics (that the object can't be used after being moved).

An alternative approach would be to move members individually:

struct ConnectionParameters
{
    string server, login, password;
    unsigned short port;
    unsigned int connectionLimit;
    unsigned long long speedLimit;

    ConnectionParameters(string server, string login, string password, unsigned short port, unsigned int connectionLimit, unsigned long long speedLimit)
        : server(move(server))
        , login(move(login))
        , password(move(password))
        , port(port)
        , connectionLimit(connectionLimit)
        , speedLimit(speedLimit) {}

    ConnectionParameters(const ConnectionParameters &) = delete;    // disable copy constructor
    ConnectionParameters(ConnectionParameters &&) = default;    // default move constructor
};

class Downloader
{
public:
    Downloader(ConnectionParameters connectionParameters, string downloadDirectory, unsigned long long diskSpaceMinimum)
        : server(move(connectionParameters.server))
        , login(move(connectionParameters.login))
        , password(move(connectionParameters.password))
        , port(connectionParameters.port)
        , connectionLimit(connectionParameters.connectionLimit)
        , speedLimit(connectionParameters.speedLimit)
        , downloadDirectory(move(downloadDirectory))
        , diskSpaceMinimum(diskSpaceMinimum) {}

private:
    string server, login, password;
    unsigned short port;
    unsigned int connectionLimit;
    unsigned long long speedLimit;
    string downloadDirectory;
    unsigned long long diskSpaceMinimum;
};

What I don't like here is that members of the ConnectionParameters structure are directly exposed (otherwise, Downloader would not be able to move them).

What is the right way to do this?

Aucun commentaire:

Enregistrer un commentaire