4

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?

shorke
  • 53
  • 4

3 Answers3

8

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;
//  ^^^^^^^^^^^^^^^^^
};
Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • I'm running VS2012 so `using` is not an option. :-) I thought that the compiler won't generate a move constructor when you define your own constructor so that the above move - in this case - would just call the provided constructor (which means 2 copies)? – shorke May 23 '13 at 20:12
  • 1
    @user2414893: According to the Standard, the compiler should generate the move constructor implicitly even if you define a regular constructor. It should not generate one if you define a copy constructor or a destructor (etc., this is a simplification). However, somehow MS is not keen on following the Standard, and it won't *ever* generate a move constructor implicitly. If you wan't it, you have to provide it. This said, I do not see how this applies to the example in your question. I thought you were talking about constructing a `Derived`, and moving a `string`, not about moving a `Derived`.. – Andy Prowl May 23 '13 at 20:15
  • @user2414893 I dunno if VS2012 counts as C++11. ;) – Yakk - Adam Nevraumont May 23 '13 at 20:20
  • Hey now, that was uncalled for! :-D Back to topic: `Derived a("A");` constructs `std::string("A")`, invokes `Derived::Derived` (first copy) and then `Base::Base` (second copy) + move in `Base::Base` (2 copies, 1 move) [assuming there is no implicit move constructor]. – shorke May 23 '13 at 20:24
  • 2
    @user2414893: If you're talking about my example, that's just one move (when initializing `Derived::Derived`'s parameter from the temporary `string`) plus one move (when passing that parameter to `Base::Base`) plus one move (when moving that into the data member). So 3 moves, no copy. – Andy Prowl May 23 '13 at 20:28
  • Andy: he was assuming no implicit move constructor. @user2414893: Well sure. But you should assume there _is_ an implicit move constructor. – Mooing Duck May 23 '13 at 20:53
  • @MooingDuck: But no implicit move constructor where, in `std::string`? What I do not understand is whether the OP is talking about copying/moving a `Derived` object or copying/moving a `string` when constructing a `Derived` object. When I answered I was assuming the former, but the last comments made me think of the latter – Andy Prowl May 23 '13 at 20:55
  • It is the latter. `Derived a("A");` <- I want to reduce the number of copies/moves here. – shorke May 23 '13 at 21:09
  • @user2414893: lol, I made confusion in my comments and swapped the roles. I meant to say I thought you were talking about the former, but if you are talking about the letter, then it is fine. It is irrelevant whether or not `Derived` has an implicit move constructor, since you are not moving a `Derived`. Only a `string` is being moved, and that does have a move constructor. – Andy Prowl May 23 '13 at 21:12
  • I just ran some tests in VS2012. It is indeed invoking the move constructor (unless the disassembly is lying :D). Looks like I just learned something about move semantics, thanks! – shorke May 23 '13 at 21:15
3

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...

David G
  • 94,763
  • 41
  • 167
  • 253
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
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.

Community
  • 1
  • 1
Jesse Good
  • 50,901
  • 14
  • 124
  • 166