Pergunta

Considering the high quality of today's compilers regarding return value optimization (both RVO and NRVO), I was wondering at what class complexity it's actually meaningful to start adding move constructors and move assignment operators.

For instance, for this really_trivial class, I just assume that move semantics cannot offer anything more than RVO and NRVO already does when copying around instances of it:

class really_trivial
{
    int first_;
    int second_;

public:

    really_trivial();
    ...
};

While in this semi_complex class, I'd add a move constructor and move assignment operator without hesitation:

class semi_complex
{
    std::vector<std::string> strings_;

public:

    semi_complex(semi_complex&& other);
    semi_complex& operator=(semi_complex&& other);
    ...
};

So, at what amount and of what kinds of member variables does it start making sense to add move constructors and move assignment operators?

Foi útil?

Solução

It is already meaningful for purely semantic reasons, even if you keep the optimization-aspects out completely. Just imagine the case where a class does not/cannot implement copying. For example boost::scoped_ptr cannot be copied, but it can be moved!

Outras dicas

In addition to the excellent answers already given, I would like to add a few forward-looking details:

There are new rules in the latest C++0x draft for automatically generating a move constructor and move assignment operator. Although this idea is not entirely new, the latest rules have only been in the draft since Oct. 2010, and not yet widely available in compilers.

If your class does not have a user-declared copy constructor, copy assignment operator, move constructor, move assignment operator and destructor, the compiler will provide defaulted move constructor and move assignment operator for you. These default implementations will simply member-wise move everything.

You can also explicitly default your move members:

semi_complex(semi_complex&&) = default;
semi_complex& operator=(semi_complex&&) = default;

Note that if you do so, you will implicitly inhibit copy semantics unless you explicitly supply or default the copy constructor and copy assignment operator.

In a closely related late-breaking change: If your class has an explicit destructor, and implicit copy members, the implicit generation of those members is now deprecated (the committee would like to remove the implicit generation of copy when there is an explicit destructor, but can't because of backwards compatibility).

So in summary, any time you have a destructor declared, you should probably be thinking about explicitly declaring both copy and move members.

As a rule of thumb I would say add a move constructor whenever you have member variables which hold (conditionally) dynamically allocated memory. In that case its often cheaper if you can just use the existing memory and give the move source the minimal allocation it needs so it can still function (meaning be destroyed). The amount of member variables doesn't matter that much, because for types not involving dynamic memory its unlikely to make a difference whether you copy or move them (even the move constructor will have to copy them from one memory location to another somehow).

Therefore move semantices make sense, when

  • dynamic memory allocation is involved
  • move makes sense for one or more member variables (which means those involve dynamic allocation somewhere along the line).

Typically, moving makes sense when the class holds some kind of resource, where a copy would involve copying that resource and moving would not. The easiest example is dynamically allocated memory. However, it is worth noting that the compiler will automatically generate move constructors and operators, just like it will for copying.

Irrespective of anything that might be automatically done by the compiler I'd say that:

  • If any member has meaningful and beneficial move semantics, that the class should have this move semantics too. (-> std::vector members)
  • If any dynamic allocations are involved when copying, move operations make sense.

Put otherwise, if move can do something more efficiently than copy, it makes sense to add it. In your really_trivial a move could only be as efficient as the copy already is.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top