Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowing users of a class to move private members

Say I have this class:

class Message
{
public:
    using Payload = std::map<std::string, boost::any>;

    Message(int id, Payload payload)
    : id_(id),
      payload_(std::move(payload))
    {}

    int id() const {return id_;}

    const Payload& payload() const {return payload_;}

private:
    int id_;
    Payload payload_;
};

where Payload could potentially be large and expensive to copy.

I would like to give users of this Message class the opportunity to move the payload instead of having to copy it. What would be the best way to go about doing this?

I can think of the following ways:

  1. Add a Payload& payload() overload which returns a mutable reference. Users can then do:

    Payload mine = std::move(message.payload())

  2. Stop pretending that I'm encapsulating payload_ and just make it a public member.

  3. Provide a takePayload member function:

    Payload takePayload() {return std::move(payload_);}

  4. Provide this alternate member function:

    void move(Payload& dest) {dest = std::move(payload_);}

  5. (Courtesy of Tavian Barnes) Provide a payload getter overload that uses a ref-qualifier:

    const Payload& payload() const {return payload_;}

    Payload payload() && {return std::move(payload_);}

Alternative #3 seems to be what's done in std::future::get, overload (1).

Any advice on what would be the best alternative (or yet another solution) would be appreciated.


EDIT: Here's some background on what I'm trying to accomplish. In my real life work, this Message class is part of some communications middleware, and contains a bunch of other meta data that the user may or may not be interested in. I had thought that the user might want to move the payload data to his or her own data structures and discard the original Message object after receiving it.

like image 432
Emile Cormier Avatar asked Apr 11 '15 00:04

Emile Cormier


People also ask

Can a class access its own private members?

Private: The class members declared as private can be accessed only by the functions inside the class. They are not allowed to be accessed directly by any object or function outside the class. Only the member functions or the friend functions are allowed to access the private data members of a class.

How do you make a private class member accessible in a different class?

We can access a private variable in a different class by putting that variable with in a Public method and calling that method from another class by creating object of that class. Example: using System; using System.

Can objects access private members of the class?

This is perfectly legal. Objects of the same type have access to one another's private members. This is because access restrictions apply at the class or type level (all instances of a class) rather than at the object level (this particular instance of a class).

What is the effect of giving a class member private access?

What is the effect of giving a class member private access? When a member of a class is declared private it can be used in only one place in a program. When a member of a class is declared private it can be used only in methods that are members of that class.


2 Answers

It seems the most consistent approach is to use option 5:

  1. The 1st and 2nd option don't seem to be advised as they expose implementation details.
  2. When using a Message rvalue, e.g., a return from a function, it should be straight forward to move the payload and it is using the 5th option:

    Messsage some_function();
    Payload playload(some_function().payload()); // moves
    
  3. Using std::move(x) with an expression conventially indicates that the value of x isn't being depended upon going forward and its content may have been transferred. The 5th option is consistent with that notation.

  4. Using the same name and having the compiler figure out whether the content can be moved makes it easier in generic contexts:

    template <typename X>
    void f(X&& message_source) {
        Payload payload(message_source.get_message());
    }
    

    Depending on whether get_message() yields an lvalue or an rvalue the payload is copied or moved appropriately. The 3rd option doesn't yield that benefit.

  5. Returning the value make it possible to use the obtained in a context where copy-elision avoids further potential copies or moves:

    return std::move(message).payload(); // copy-elision enabled
    

    This is something the 4th option doesn't yield.

On the negative side of the balance sheet it is easy to incorrectly attempt moving the payload:

return std::move(message.payload()); // whoops - this copies!

Note, that the other overload for the 5th option needs be to be declared differently, though:

Payload        payload() &&     { return std::move(this->payload_); }
Payload const& payload() const& { return this->payload_; }
          // this is needed --^
like image 87
Dietmar Kühl Avatar answered Sep 18 '22 08:09

Dietmar Kühl


The first thing I'd recommend is don't do this at all, if you can help it. Allowing your private data members to be moved from breaks encapsulation (even worse than returning const references to them). In my ~35,000 line codebase I have needed to do this precisely once.

That being said, I did need to do it once, and there were measurable and significant performance advantages to doing it. Here's my opinion on each of your suggested approaches:

  1. Add a Payload& payload() overload which returns a mutable reference. Users can then do:

    Payload mine = std::move(message.payload())

The downside here is that users can do message.payload() = something else;, which can mess with your invariants.

  1. Stop pretending that I'm encapsulating payload_ and just make it a public member.

Encapsulation isn't an all-or-nothing thing; IMO you should strive for as much encapsulation as is possible, or at least reasonable.

  1. Provide a takePayload member function:

Payload takePayload() {return std::move(payload_);}

This is my favourite solution if you don't have access to ref-qualifiers (seriously VC++?). I would probably name it movePayload(). Some may even prefer it to option 5 because it is more explicit and less confusing.

  1. Provide this alternate member function:

void move(Payload& dest) {dest = std::move(payload_);}

Why use an out parameter when a return value will do?

  1. (Courtesy of Tavian Barnes) Provide a payload getter overload that uses a ref-qualifier:

const Payload& payload() const {return payload_;}

Payload payload() && {return std::move(payload_);}

Somewhat unsurprisingly, this is my favourite suggestion :). Note that you'll have to write

const Payload& payload() const& { return payload_; }
Payload payload() && { return std::move(payload_); }

(const& instead of const) or the compiler will complain about ambiguous overloads.

This idiom is not without some caveats, although I believe Scott Meyers suggests it in one of his books so it can't be too bad. One of them is that the caller syntax is odd:

Message message;
Payload payload = std::move(message).payload();

It would be even worse if you needed to move two values out of a Message; the double std::move() would be very confusing to someone unfamiliar with the pattern.

On the other hand, this comes with a lot more safety than other methods, because you can only move from Messages that are seen to be xvalues.

There is a minor variation:

Payload&& payload() && { return std::move(payload_); }

I'm really not sure which one is better. Due to copy elision, the performance of both should be the same. They do have different behaviour when the return value is ignored.

like image 26
Tavian Barnes Avatar answered Sep 20 '22 08:09

Tavian Barnes