3

Note I am asking this as a std::string-specific question, not a general how to pass an object one.

I would delete this question but I am not allowed to because of the answers it has. I believe the answers may have tended to degenerate into answering the more general question of "What's the best way to pass an object in C++?" There are many duplicates of these more general answers on stack overflow even if this precise question may not itself be a dup. This degeneration may be because the question is an ill-posed one, and there is nothing special about the std::string class. This question is not highly up-voted, which suggests it is not interesting. My bad in that case. Perhaps a mod will see this black text, take pity on me and kill this question.

Which of the following signatures represents the fastest way to pass a non-const std::string instance into a function which does not modify it, taking into account the overall overhead of the call at the calling site including whether a deep-copy of the underlying character array would be generated prior to the call?

extern void eat_string_by_value          (std::string s);
extern void eat_const_string_by_value    (const std::string s);
extern void eat_string_by_reference      (std::string& s);
extern void eat_const_string_by_reference(const std::string& s);

Note I am asking this as a std::string-specific question, not a general how to pass an object one. In particular, my question is spurred by:

  • std::string handle-body implementation: Do any of the signatures imply a deep copy of the string body and its backing character array at the calling site?
    • Note the difference between copying the handle (which should be lightweight) and copying the body (which would involve memory allocation).
  • C++98 versus C++>=11: Is there a different answer either side of this version divide?

I do not see my question as a duplicate of this previous one which was itself marked as a duplicate of a general object passing one< Pass arguments as std::string or const std::string&? > because of these type-specific detailed points.

An interesting answer on a related question:

Community
  • 1
  • 1
ahcox
  • 9,349
  • 5
  • 33
  • 38
  • 4
    Have you measured it? – Baum mit Augen Dec 09 '14 at 20:54
  • 7
    Take const ref, unless the function is going to make a copy of the string internally, in which case the answer is "it's complicated". – T.C. Dec 09 '14 at 20:55
  • 2
    Why would you pass non-const to a function that doesn't modify the argument? – jterrace Dec 09 '14 at 20:55
  • @BaummitAugen Sure, on one compiler. I want to hear people's reasoning about what results I should see. Also, there is some advice from a book I read a decade ago rattling around somewhere in the back of my mind, I think my results don't match that advice but I can't quite remember where I read it. Maybe someone will quote something that jogs my memory of that book. – ahcox Dec 09 '14 at 20:58
  • @jterrace To print it, for example? – Baum mit Augen Dec 09 '14 at 20:59
  • 1
    Why do you need non-const to print? – jterrace Dec 09 '14 at 21:00
  • @jterrace For general type I obviously wouldn't. For std::string it is about this business of probing which signatures trigger the deep copy at the call site on common string implementations on various compiler versions. I am wondering if they make a difference to that. – ahcox Dec 09 '14 at 21:03
  • @jterrace You don't need non-const to print, but if something happens to be non-const, why would you not be able to print it? – Baum mit Augen Dec 09 '14 at 21:03
  • What? I'm asking the OP why he wants "to pass a non-const std::string instance into a function which does not modify it". If the function is not modifying the string, it should be passed as const. – jterrace Dec 09 '14 at 21:10
  • You shouldn't pick on way solely on the premise that is it going to be faster but rather because it makes sense regarding your current code design. **Then**, once you got a working code that actually needs optimizing, profile and optimize. – ereOn Dec 09 '14 at 21:20
  • Depending on the function body and compiler settings, the optimizer may eliminate the call completely by inlining. If you use whole program optimization, this can also be done for functions in different compilation units. It is also probably dependent on the size of the string because many implementations do an optimization for small (usually less four chars) strings. – Jens Dec 09 '14 at 21:53
  • @Jens 4 chars sounds a little too small. I believe libc++'s limit is about three words (the computing sense, not the colloquial sense). – T.C. Dec 09 '14 at 22:48
  • 1
    [this](http://stackoverflow.com/q/10231349/476681) is a similar question – BЈовић Dec 10 '14 at 08:00
  • @T.C. Yes, you are right. On Visual C++, its 16 chars. – Jens Dec 11 '14 at 21:21

2 Answers2

7
extern void eat_string_by_value          (std::string s);        // value
extern void eat_const_string_by_value    (const std::string s);
extern void eat_string_by_reference      (std::string& s);       // reference
extern void eat_const_string_by_reference(const std::string& s); // const-ref

First off, you missed one option (since C++11):

extern void eat_string_by_rvaluereference(std::string&& s);      // rvalue-ref

Next point, two of your options are identical, because top-level argument-cv-qualifiers are not part of the function signature:

extern void eat_string_by_value          (std::string s);
extern void eat_const_string_by_value    (const std::string s);

So, let's look:

  1. Passing by value:
    Disadvantage: The caller must copy or move into the argument. Unless it gets constructed there.
    Advantage: Binds to all. Callee has its own copy to modify.
  2. Passing by reference: Disadvantage: Only binds to lvalues (non-temporary objects).
    Advantage: Callee can modify the passed argument.
  3. Passing by const-ref. Disadvantage: Callee must make its own copy if needed.
    Advantages: Binds to all.
  4. Passing by rvalue-ref.
    Disadvantage: Cannot bind to lvalues. Copy if binding to const objects.
    Advantages: Can scavenge the passed argument.

So, by-value nearly always means a copy (or at least a move), by-reference means never a copy (but unneccessary restrictions on the argument), all the rest only copy if conversion is neccessary.
By-const-ref means the argument cannot be modified, and by-rvalue-ref means its resources can be re-used, which might save copies later-on.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
2

Which of the following signatures represents the fastest way to pass a non-const std::string instance into a function which does not modify it, taking into account the overall overhead of the call at the calling site including whether a deep-copy of the underlying character array would be generated prior to the call?

It depends. There is no single way which is best for all cases, in all regards.

The primary variables:

  • what are you passing? The length of the strings can affect which form will outperform the others.
  • the compiler
  • the compiler options and language standard
  • the standard library
  • would a copy be required at the callsite?
  • what happens to the string in the function?
  • passing by value can open up some possibilities for optimization, but the cost to copy is often higher.

There is not one answer/signature which is ideal for every case.

std::string handle-body implementation: Do any of the signatures imply a deep copy of the string body and its backing character array at the calling site?

Some implementations used COW (Copy On Write), but that is no longer valid in C++11. Passing values will require a copy. The copy may be fast (=no heap allocation) for short strings, if SSO is implemented by your library.

C++98 versus C++>=11: Is there a different answer either side of this version divide?

As mentioned above, COW is no longer valid. You may see very different performance characteristics.

justin
  • 104,054
  • 14
  • 179
  • 226