C++11: call by value, move semantics and inheritance

馋奶兔 提交于 2019-12-07 10:49:15

问题


Let's say I have a class which I plan to directly expose as an instantiatable class to the programmer:

class Base
{
public:
    Base(std::string text) : m_text(std::move(text)) {}
private:
    std::string m_text;
};

So far so good. No need for a rvalue-constructor here. Now, at some point in the future, I decide to extend Base:

class Derived : public Base
{
public:
    Derived(const std::string &text) : Base(text) {}
};

This bugs me: I can't take the string by value in Derived because that's what Base is already doing - I'd end up with 2 copies and 1 move. The const-reference constructor here also performs an unnecessary copy on rvalues.

The question: how do I copy + move only once (like the simple constructor in Base did) without adding more constructors?


回答1:


You can't copy and move only once, unless you change the design of your classes and turn their constructors into (possibly SFINAE-constrained) templated forwarding constructors (Yakk's answer shows how).

While doing that would make it possible to perform just one move and no copy when rvalues are provided, and one copy and no moves when lvalues are provided, it is an overkill in most situations.

As an alternative to the template-based forwarding constructors, you could provide two constructors, both in your base class and in your derived class: one for rvalue references and one for lvalue references to const. But again, that's an unnecessary complication in most scenarios (and doesn't scale well when the number of arguments increases, since the number of required constructors would increase exponentially).

Moving an std::string is as fast as copying a pointer and an integer (ignoring SSO optimization here), and you should not bother about it unless you have real evidence that this is a bottleneck that prevents your application from meeting its performance requirements (hard to believe).

Therefore, just let your Derived constructor take its argument by value unconditionally, and move it when passing it to the base class's constructor:

class Derived : public Base
{
public:
    Derived(std::string text) : Base(std::move(text)) { }
};

Another option, in case you want (or accept) Derive to inherit all of Base's constructors, is to exploit C++11's inherited constructors like so:

class Derived : public Base
{
public:
    using Base::Base;
//  ^^^^^^^^^^^^^^^^^
};



回答2:


Perfect forwarding with a SFINAE constructor:

class Derived : public Base
{
public:
  template<
    typename T,
    typename=typename std::enable_if<
      std::is_constructible<std::string, T&&>::value
    >::type
  >
  Derived(T&& text) : Base(std::forward<T>(text)) {}
};

A downside (beyond the insanity of the above construct) is that this will participate in overload resolution to an overly greedy degree: it is a nearly perfect match for any argument that can be converted to a std::string. So if you had another constructor that took a char const* it might be ignored if you passed in a char const* variable, because T&& might match better.

And woe betide you if your Derived or Base has an operator std::string...




回答3:


Change Derived to the following:

class Derived : public Base
{
public:
    using Base::Base;
};

Now Derived inherits the constructors from Base.

See Forwarding all constructors in C++0x for some more info.



来源:https://stackoverflow.com/questions/16722614/c11-call-by-value-move-semantics-and-inheritance

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!