1

I see this subject was discussed in StackOverflow, but I could not find the right answer.

I've been using C++ for many years, and I am still in doubt about how to write a simple setter method for a class which needs to set the value of a string.

class Content { public: void setXml(??? xml) { xml_ = xml; } private: std::string xml_; }; 

There can be two possible usages: one when I want to copy the original string, the other when I want to move it, ie:

std::string xml = getXml(); Content content; content.setXml(xml); // copy 

Or:

content.setXml(std::move(xml)); // move 

Or:

content.setXml(getXML()); // move 

Do I need to define two separate setter methods in class Content, like this?

void setXml(const std::string& xml) { xml_ = xml; } void setXml(std::string&& xml) { xml_ = std::move(xml); } 

Or use perfect forwarding?

template<class String> void setXml(String&& xml) { xml_ = std::forward<String&&>(xml); } 

My aim is to avoid unnecessary copying/allocation of the string (as it can be very long) for strings which can be moved, however for string that must be copied, I need to allow copying functionality as well.

Am I right to say that, in this case, copy elision is not going to happen, as it is only in case of returning objects from a function?

2
  • 1
    std::string is in itself already a container. Do you really need to wrap a container inside another container? Perhaps you could rework your design so that you don't need a container-container, and therefore not need a "setter" (which in the majority of cases tend to be a sign of bad design, IMO). Commented May 30, 2024 at 17:40
  • @Someprogrammerdude vector or map are also containers and they can contain strings. They have same issue when adding new item (e.g.string) to themselves. So they do overload push_back(const T&) and push_back(T&&). Commented Jun 1, 2024 at 15:12

1 Answer 1

2

You can write one setter that accept the parameter by value, and then moves from the parameter into the class member:

void setXml(std::string xml) { xml_ = std::move(xml); } 

This will allow the caller to decide whether to copy or move into the parameter, exactly as you showed, since std::string has constructors for both actions.

content.setXml(xml); // 1 copy and 1 move content.setXml(std::move(xml)); // 2 moves, no copies content.setXml(getXML()); // 2 moves, no copies 
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for this, but then why STL containers don't do the same? I can see std::vector overloads: void push_back( const T&); void push_back( T&&); and std::list does the same.
@AndreyRubliov probably for historical reasons. Move semantics were added in C++11. The standard containers predate that, so they already supported copying push_back(), so they just added moving push_back().
@AndreyRubliov, as mentioned in item 41 of Meyer's Effective modern C++, you should only consider pass by value for copyable parameters that are cheap to move and always copied. If push_back() had been implemented as pass by value, that would lead to one extra move for both lvalues and rvalues (one copy into the parameter and then one move of the parameter, and one move into the parameter and one move of the parameter, respectively). Legacy or not, there's also the performance dimension that would justify maintaining multiple overloads.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.