1

Let's say I have a buffer of chars in memory that holds a c_string, and I want to add an object of std::string with the content of that c_string to a standard container, such as std::list<std::string>, in an efficient way.

Example:

#include <list>
#include <string>

int main()
{
    std::list<std::string> list;

    char c_string_buffer[] {"For example"};

    list.push_back(c_string_buffer); // 1

    list.emplace_back(c_string_buffer); // 2

    list.push_back(std::move(std::string(c_string_buffer))); // 3
}

I use ReSharper C++, and it complains about #1 (and suggests #2).

When I read push_back vs emplace_back, it says that when it is not an rvalue, the container will store a "copied" copy, not a moved copy. Meaning, #2 does the same as #1. Don’t blindly prefer emplace_back to push_back also talks about that.

Case 3: When I read What's wrong with moving?, it says that what std::move() does "is a cast to a rvalue reference, which may enable moving under some conditions".

--

Does #3 actually give any benefit? I assume that the constructor of std::string is called and creates a std::string object with the content of the c_string. I am not sure if later the container constructs another std::string and copies the 1st object to the 2nd object.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Amit G.
  • 2,546
  • 2
  • 22
  • 30
  • 1
    The first link talks about a faulty implementation of `emplace_back` in an older version of MSVC. In the current version it correctly constructs a new `std::string` in place – UnholySheep Mar 02 '22 at 20:58
  • for 3, `std::move` is not necessary, a temporary is already an r-value – Alan Birtles Mar 02 '22 at 21:06
  • you could try it and and see what the difference is, it would be interesting to see – pm100 Mar 02 '22 at 21:47
  • Did you actually read the article you cited? It isn’t saying don’t use emplace, it’s saying the students didn’t fully understand how to take advantage of the suggestion and that the tooling didn’t catch the issue after they tried to implement its suggestion – Taekahn Mar 02 '22 at 23:03

2 Answers2

3

// 3 is fully equivalent to // 1. std::move does absolutely nothing here since std::string(c_string_buffer) is already a rvalue.

The problem with push_back is not related to move vs copy.

push_back is always a bad choice if you don't yet have an object of the element type because it always creates the new container element via copy or move construction from another object of the element type.

If you write list.push_back(c_string_buffer); // 1, then because push_back expects a std::string&& argument (or const std::string&), a temporary object of type std::string will be constructed from c_string_buffer and passed-by-reference to push_back. push_back then constructs the new element from this temporary.

With // 3 you are just making the temporary construction that would otherwise happen implicitly explicit.

The second step above can be avoided completely by emplace_back. Instead of taking a reference to an object of the target type, it takes arbitrary arguments by-reference and then constructs the new element directly from the arguments. No temporary std::string is needed.

user17732522
  • 53,019
  • 2
  • 56
  • 105
1

Does 3 actually give any benefit?

No. #1 already does a move without std::move. #3 is just an unnecessarily explicit way to write #1.

#2 is generally potentially most efficient. That's why your static analyzer suggests it. But, as the article explains, it's not significant in this case, and has a potential compile-time penalty. I believe you can avoid the potential compile-time cost by using list.emplace_back(+c_string_buffer);, but that may be confusing to the reader.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
eerorika
  • 232,697
  • 12
  • 197
  • 326