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

    If Copy Always Needed Copy Needed

    Box by_value(std::string id) {
      // sanitize id (already a copy)
      replace(begin(id), end(id), 
              ' ', '-');
      return Box{std::move(id)};
    }
    Box by_cref(std::string const& id) {
      std::string idCopy {id};
      replace(begin(idCopy), end(idCopy), 
              ' ', '-');
      return Box{std::move(idCopy)};
    }
    Box b = by_value("A 2");

    implicitly creates one string object:

    • function parameter string id{"A 2"}
    Box b = by_cref("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
    std::string myId = "B 7";
    Box b = by_value(std::move(myId));

    does not create an additional string:

    • myId is moved into function parameter id
    Box b = by_cref(std::move(myId));

    creates one additional string object:

    • explicit local copy idCopy;
    • Note: std::move has no effect here!

    Don't know what std::move does / is for? Refer to article: Move Semantics

    std::string myId = "K 9";
    Box b = by_value(myId);
    • creates one additional string object:
    • function parameter string id{myId} as copy of myId.
    Box b = by_cref(myId);

    creates one additional string object:

    • explicit local copy idCopy

    Read-Only Access (modern) Read-Only C++17

    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
    }

    by avoiding a level of indirection:

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

    Read-Only Access (legacy) Read-Only C++98-14

    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

    In-Place Modification Of Input String In-Place Modification Out Parameter

    #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.