Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 645
shorke Avatar asked Dec 09 '22 15:12

shorke


1 Answers

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;
//  ^^^^^^^^^^^^^^^^^
};
like image 99
Andy Prowl Avatar answered Dec 11 '22 12:12

Andy Prowl