Home > front end >  How to efficiently pass a string to a function if I will certainly copy it? C 20
How to efficiently pass a string to a function if I will certainly copy it? C 20

Time:09-10

I would like to know how to properly handle a string input in a function, if I know I will have to make a copy inside of it to place it inside a container.

I was thinking of doing it like this:

void foo(const string& s){
    container.push(s);
}

But if I pass a char* string I will make 2 copies, first when calling foo, then when passing it to a container.

My second idea was to use a string_view, as it is often said, that if you can use a view, you should. So my second version of the function looked like this:

void foo2(string_view s){
    container.push(string(s));
}

Ok, now I will make only a single copy no matter what type of string I am given. But then I started thinking, why couldn't I just accept a string as an argument to a function, simplifying it like this:

void foo3(string s){
    container.push(std::move(s));
}

But now I have to make sure, that my container properly utilizes move semantics so it doesn't end up making another copy! As this container object is a templated type it means using perfect forwarding and so on, which is whole lot of work in itself.

At this point I have no idea which option to choose, so I would like to ask you for some advice. I also feel like I am way overthinking this, as this probably won't have that much of a performance impact in the end, but I would like to do it right. Thanks in advance.

CodePudding user response:

You know that you definitely need a std::string in the end. Not something string like, exactly a std::string. In this case it doesn't make sense to take as argument anything else than a std::string. There are exceptions to this, e.g. if all of your exposed interface you use std::string_view then it might make sense to do here as well.

Now the choices are const std::string& (lvalue ref), std::string&& (rvalue ref) and std::string (value). In this case where you know you need a new object to pass to the level below the recommended one is: value std::move.

This way:

  • if the user has something else than a std::string you get 1 conversion 1 move
  • if the user has std::string you get 1 copy 1 move (or 1 move 1 move if the user does std::move at the call site).

But now I have to make sure, that my container properly utilizes move semantics

No, not "now". This shouldn't be the point where you start to consider move semantics for your container. A container most likely should have proper move semantics just on the merit of it being ... well a container. You don't want to copy 5k elements when you could have moved them instead.

CodePudding user response:

A fourth option would be to use perfect forwarding and pass the arguments to foo as-is to the container's emplace/emplace_back function:

template<class... Args>
requires std::constructible_from<std::string, Args...>
void foo4(Args&&... args){ // a forwarding reference pack
    container.emplace(std::forward<Args>(args)...);
}
  1. If you pass a temporary std::string to foo4, it'll use the std::string move constructor. Nothing else.
  2. If you pass an lvalue reference to foo4, it'll use the std::string copy constructor. Nothing else.
  3. If you pass other arguments that can be used to construct a std::string, like a std::string_view, it'll be constructed in-place in the container. Not even a copy or move will be needed.

Demo


You could do equally well for the first two cases above by manually creating the overloads needed to avoid unneeded instantiations.

void foo4(const std::string& f) { container.push(f); } // one copy
void foo4(std::string&& f) { container.push(std::move(f)); } // one move

It would however not allow for the the third case to be as effective. If you don't care about, or don't want to support, the third case, you could go for the two overloads that mimics what a copy constructor/move constructor pair does. Since you asked about how to "efficiently pass a string" I think that adding an overload is a small price to pay.

  • Related