What is the most efficient way to set class variable using rvalue in c++?

274 Views Asked by At

I just started working with c++11 r-values. I read some tutorials, but I haven't found the answer.

What is the best way (the most efficient way) to set a class variable? Is below code correct or not? (let's assume std::string has defined move constructor and assignment operator).

class StringWrapper
{
private:
    std::string str_;

public:
    StringWrapper() : str_("") {}

    void setString1(std::string&& str) {
      str_ = std::move(str);
    }

    void setString2(const std::string& str) {
      str_ = std::move(str);
    }

    // other possibility?
};

int main() {
    std::string myText("text");

    StringWrapper x1, x2;

    x1.setString?("text"); // I guess here should be setString1
    x2.setString?(myText); // I guess here should be setString2
}

I know that compiler can optimize my code and/or I can use overload functions. I'd like to only know what is the best way.

4

There are 4 best solutions below

1
Chris Drew On BEST ANSWER

Herb Sutter's advice on this is to start with the standard C++98 approach:

void setString(const std::string& str) {
  str_ = str;
}

And if you need to optimize for rvalues add an overload that takes an rvalue reference:

void setString(std::string&& str) noexcept {
  str_ = std::move(str);
}

Note that most implementations of std::string use the small string optimization so that if your strings are small a move is the same as a copy anyway and you wouldn't get any benefit.

It is tempting to use pass-by-value and then move (as in Adam Hunyadi's answer) to avoid having to write multiple overloads. But Herb pointed out that it does not re-use any existing capacity of str_. If you call it multiple times with lvalues it will allocate a new string each time. If you have a const std::string& overload then it can re-use existing capacity and avoid allocations.

If you are really clever you can use a templated setter that uses perfect forwarding but to get it completely correct is actually quite complicated.

8
Bathsheba On

Compiler designers are clever folk. Use the crystal clear and therefore maintainable

void setString(const std::string& str) {
    str_ = str;
}

and let the compiler worry about optimisations. Pretty please, with sugar on top.

Better still, don't masquerade code as being encapsulated. If you intend to provide such a method, then why not simply make str_ public? (Unless you intend to make other adjustments to your object if the member changes.)

Finally, why don't you like the default constructor of std::string? Ditch str_("").

6
Adam Hunyadi On

The version with rvalue reference would not normally bind to an lvalue (in your case, mytext), you would have to move it, and therefore construct the object twice, leaving you with a dangerous object. A const lvalue reference should be slower when constructing from an rvalue, because it would do the same thing again: construct -> move -> move construct.

The compiler could possibly optimize the overhead away though.

Your best bet would actually be:

void setString(std::string str) 
{
   str_ = std::move(str);
}

The compiler here is suprisingly guaranteed to deduce the type of the argument and call the copy constructor for lvalues and the move constructor for rvalues.

Update:

Chris Dew pointed out that constructing and move assigning a string is actually more expensive than copy constructing. I am now convinced that using a const& argument is the better option. :D

1
Edgar Rokjān On

You might probably use templatized setString and forwarding references:

class StringWrapper
{
private:
    std::string str_;

public:
    template<typename T>
    void setString(T&& str) {
        str_ = std::forward<T>(str);
    }
};