◐ Shell
clean mode source ↗

Passing Strings To Functions C++

Which String Parameter Type? Which String Parameter? String Parameters

Summary

If You…

Use Parameter Type

always need a copy of the input string inside the function std::string
pass by value
want read-only access
  • don't (always) need a copy
  • are using C++17 / 20
#include <string_view> std::string_view
want read-only access
  • don't (always) need a copy
  • are stuck with C++98 / 11 / 14
std::string const&
pass by const reference
want the function to modify the input string in-place (you should try to avoid such output parameters) std::string &
pass by (non-const) reference

Pass By Value!

By value ⇒
One copy per call

Box by_value (std::string id) {
  // sanitize id (already a copy)
  replace(begin(id), end(id), 
          ' ', '-');
  
  return Box{std::move(id)};
}

By const reference ⇒
Sometimes two copies

Box by_cref (std::string const& id) {
  // make copy for replacing
  std::string idCopy {id};
  replace(begin(idCopy), end(idCopy), 
          ' ', '-');
  return Box{std::move(idCopy)};
}

implicitly creates one string object:

  • function parameter string id{"A 2"}

creates two string objects:

  1. implicit string{"A 2"} that is bound to function parameter string const& id
  2. explicit local copy idCopy
Box b = by_value(std::string{myId});

does not create an additional string:

  • temporary string object is moved into function parameter id
Box b = by_cref(std::string{myId});

creates one additional string object:

  • explicit local copy idCopy
std::string myId = "K 9";
  • creates one additional string object:
  • function parameter string id{myId} as copy of myId.

creates one additional string object:

  • explicit local copy idCopy

Use std::string_viewC++17

  • lightweight  (= cheap to copy, can be passed by value)
  • non-owning  (= not responsible for allocating or deleting memory)
  • read-only view
  • of a character range or string(-like) object  (std::string / "literal" / …)
#include <string>
#include <string_view>
class Pattern { 
public: 
  auto match (std::string_view s) const {  }
};

Pattern p {"ab.*"}; auto r1 = p.match("abx"); // no copy std::string txt = "abcde"; auto r2 = p.match(txt); // no copy

string_view avoids unnecessary allocations string_view avoids allocations Allocations

We don't want/expect additional copies or memory allocations for a read-only parameter!

The traditional choice std::string const& is problematic:

  • A std::string can be constructed from string literals or an iterator range (to a char sequence).
  • If we pass an object as function argument that is not a string itself, but something that can be used to construct a string, e.g., a string literal or an iterator range, a new temporary string object will be allocated and bound to the const reference.
#include <vector>
#include <string>
#include <string_view>

void f_cref (std::string const& s) { … }
void f_view (std::string_view s) { … }

int main () {
  std::string stdStr = "Standard String";
  auto const cStr = "C-String";
  std::vector<char> v {'c','h','a','r','s'};
  f_cref(stdStr);     // no copy
  f_cref(cStr);       //  temp copy
  f_cref("Literal");  //  temp copy
  f_cref({begin(v),end(v)});  //  temp copy
  f_view(stdStr);     // no copy
  f_view(cStr);       //  no copy
  f_view("Literal");  //  no copy
  f_view({begin(v),end(v)});  //  no copy
}

string_view can speed up accesses

by avoiding a level of indirection:

string_view can have one fewer indirection than a const reference to the actual string storage

Use 2 Overloads: string const& and char const*

to avoid temporary copies when string literals are used as input

  • works in all C++ standards
  • char const* avoids unnecessary copies when literals are passed
  • more code
  • error-prone C-style pointer interface
#include <string>
class Pattern { 
public: 
  // takes string objects
  auto match (std::string const& s) const { 
    return match(s.data());  // relay to 2nd overload
  }
  // takes string literals / C-strings
  auto match (char const* s) const {  }
};

Pattern p {"ab.*"}; auto r1 = p.match("abx"); // no copy std::string txt = "abcde"; auto r2 = p.match(txt); // no copy
#include <iostream>
#include <string>
void replace_umlauts (std::string& s) {  }
void test_replace_umlauts () {
  std::string s = "Ölüberschussländer";
  replace_umlauts(s);
  assert(s == "Oelueberschusslaender");  
}
You should avoid output parameters for in-place modification in general:
  • side effects (here: change of string) not obvious at the call site
  • harder to reason about correctness
  • harder to use such functions in parallel programs

However, sometimes reference parameters can be beneficial for avoiding excessive copies / memory allocations by re-using the same object's memory multiple times.

Last updated: 2022-03-09

Found this useful? Share it: