作为参数通过 const std: : string 的日子结束了吗?

I heard a recent talk by Herb Sutter who suggested that the reasons to pass std::vector and std::string by const & are largely gone. He suggested that writing a function such as the following is now preferable:

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... do stuff ...
   return return_val;
}

I understand that the return_val will be an rvalue at the point the function returns and can therefore be returned using move semantics, which are very cheap. However, inval is still much larger than the size of a reference (which is usually implemented as a pointer). This is because a std::string has various components including a pointer into the heap and a member char[] for short string optimization. So it seems to me that passing by reference is still a good idea.

Can anyone explain why Herb might have said this?

转载于:https://stackoverflow.com/questions/10231349/are-the-days-of-passing-const-stdstring-as-a-parameter-over

c++
csdnceshi65
larry*wei At the very same time (within a minute) that he posted his comment saying that it's subjective! That makes it all the worse.
大约 5 年之前 回复
csdnceshi75
衫裤跑路 Don't be too hard on him, he's also the guy who gave the best answer ;-)
大约 5 年之前 回复
csdnceshi65
larry*wei I still smh at the absurdity of the claim that any question of whether something is a good idea and any question on which there are answers that disagree is necessarily subjective. I wonder whether the guy who wrote that ever revisits this and feels some embarrassment.
大约 5 年之前 回复
csdnceshi79
python小菜 It's still available on the web archive.
5 年多之前 回复
weixin_41568126
乱世@小熊 quite old post already... anyhow, the link to Dave Abrahams's article seems to be dead. Do you know where else to find it?
5 年多之前 回复
csdnceshi65
larry*wei "Calling it subjective is nonsense." -- Indeed, and that's putting it politely. The claim that any assertion of what is a good idea is "subjective" (in a sense that would make it inappropriate to SO) is objectively false and ludicrous. It's a good idea not to omit all spaces from your program and to use sensible names unless trying to win an obfuscation contest. It's a good idea to free memory rather than leak it. And so on and so on. And it's a good idea not to use bogus sophistic arguments to rationalize a bad vote to close.
7 年多之前 回复
csdnceshi54
hurriedly% There is nothing off-topic, non-constructive, or subjective here: it's a very clear question about programming and the C++ language, about what is more efficient.
8 年多之前 回复
csdnceshi51
旧行李 Bolas: this is a "good practice" question with a technical explanation. It's not because "it makes code cleaner" or improves other unmeasurable quantities such as "maintainability" or the like. Here, actual noticeable and measurable performance improvements can be gained. Calling it subjective is nonsense.
8 年多之前 回复
csdnceshi75
衫裤跑路 Mind you I guess all subjective questions/answers are made up of a set of objective truths. What makes the overall question subjective or objective is whether the set of objective components to the answer are a finite set and can be shown to only lead to one conclusion.
8 年多之前 回复
csdnceshi75
衫裤跑路 - Actually that isn't subjective is it, that's just me not knowning the answer to the question and therefore expressing myself in vague terms. The answer provided by the kind members of SO is yes in these circumstances (not subjective) and no in these other circumstances (not subjective).
8 年多之前 回复
csdnceshi75
衫裤跑路 - I suppose the title of my question is subjective in that it asks whether things should be done this way now. However, what i wanted to understand is the facts behind what has changed. These facts are not subjective in themselves.
8 年多之前 回复
csdnceshi52
妄徒之命 What constitutes "good practice" is subjective. Just look at the answers: some agree with Herb, some agree conditionally with Herb. Who's right and who's wrong is up to you. It's a subjective question.
8 年多之前 回复
csdnceshi75
衫裤跑路 How is it subjective? C++ has changed, meaning that there is a specific single reason why this behavior is now good practice in certain circumstances.
8 年多之前 回复
weixin_41568131
10.24 This is an exact duplicate of stackoverflow.com/q/9952622/576911
8 年多之前 回复
csdnceshi52
妄徒之命 It's not constructive in the sense that it's a subjective question, as most coding style questions are. And the OT close is probably someone thinking it should be on Programmers.SE, due to the subjective nature.
8 年多之前 回复
csdnceshi75
衫裤跑路 Fascinating, so if you're going to have to make a copy anyway, pass-by-value is likely faster than pass-by-reference.
8 年多之前 回复
csdnceshi75
衫裤跑路 Excellent link thanks, I'm not sure why this is getting close markers, how is this off topic for C++
8 年多之前 回复
csdnceshi79
python小菜 I think the best answer to the question is probably to read Dave Abrahams's article about it on C++ Next. I'd add that I see nothing about this that qualifies as off-topic or not constructive. It's a clear question, about programming, to which there are factual answers.
8 年多之前 回复

11个回答

The reason Herb said what he said is because of cases like this.

Let's say I have function A which calls function B, which calls function C. And A passes a string through B and into C. A does not know or care about C; all A knows about is B. That is, C is an implementation detail of B.

Let's say that A is defined as follows:

void A()
{
  B("value");
}

If B and C take the string by const&, then it looks something like this:

void B(const std::string &str)
{
  C(str);
}

void C(const std::string &str)
{
  //Do something with `str`. Does not store it.
}

All well and good. You're just passing pointers around, no copying, no moving, everyone's happy. C takes a const& because it doesn't store the string. It simply uses it.

Now, I want to make one simple change: C needs to store the string somewhere.

void C(const std::string &str)
{
  //Do something with `str`.
  m_str = str;
}

Hello, copy constructor and potential memory allocation (ignore the Short String Optimization (SSO)). C++11's move semantics are supposed to make it possible to remove needless copy-constructing, right? And A passes a temporary; there's no reason why C should have to copy the data. It should just abscond with what was given to it.

Except it can't. Because it takes a const&.

If I change C to take its parameter by value, that just causes B to do the copy into that parameter; I gain nothing.

So if I had just passed str by value through all of the functions, relying on std::move to shuffle the data around, we wouldn't have this problem. If someone wants to hold on to it, they can. If they don't, oh well.

Is it more expensive? Yes; moving into a value is more expensive than using references. Is it less expensive than the copy? Not for small strings with SSO. Is it worth doing?

It depends on your use case. How much do you hate memory allocations?

csdnceshi53
Lotus@ Why would the string be moved from B to C in the by value case? If B is B(std::string b) and C is C(std::string c) then either we have to call C(std::move(b)) in B or b has to remain unchanged (thus 'unmoved from') until exiting B. (Perhaps an optimizing compiler will move the string under the as-if rule if b isn't used after the call but I don't think there is a strong guarantee.) The same is true for the copy of str to m_str. Even if a function paramter was initialized with an rvalue it is an lvalue inside the function and std::move is required to move from that lvalue.
大约 5 年之前 回复
weixin_41568126
乱世@小熊 Has nobody pointed out that by this reasoning, passing by & is the best of both passing by const& and by std::move? It's faster than std::move, but still allows C to optionally steal the string. (the only disadvantage is not being able to pass in a temporary, which is the point of the question, I admit).
大约 5 年之前 回复
weixin_41568134
MAO-EYE Sorry to nitpick, but you don't specify the type of m_str anywhere in your example. If it were a const std::string&, there would be no copy constructor, no overhead. If you make its type explicit, your answer will be more comprehensible to those new to C++.
5 年多之前 回复
csdnceshi80
胖鸭 Potential error in the answer. Perhaps this answer should say 'copy assign' rather than 'copy construct'. If you're going to copy construct from a const & argument, then it might be simpler and better to take it by value. But if you're going to copy assign, then alternatives might be better. Either way, before we debate, let's distinguish copy-construct and copy-assign
5 年多之前 回复
weixin_41568127
?yb? First, that still doesn't explain where you get "will do 3 copies" from, unless you're backtracking on that. Second, yes, if the user does not move an l-value into the parameter, then it will be copied. If the function is going to copy the string to some internal object anyway, at least this way, the user decides if it will be a copy or a move. Which is the whole point of my answer.
7 年多之前 回复
csdnceshi68
local-host Yes, I understand with const&, there are no copies but when it is replaced with value as shown in the question, there will be 0 copies for an rvalue but 2 copies for an lvalue(last one can be moved).
7 年多之前 回复
weixin_41568127
?yb? No it isn't. The above definitions for B and C take their value by const&. And the proposed ones that take their value by value will move their values from their arguments to the various destinations. So no copying will take place.
7 年多之前 回复
csdnceshi68
local-host From B it is copied to C and inside C it is copied into m_str
7 年多之前 回复
weixin_41568127
?yb? Where do you get 3 copies from? The literal is copied into s, and s is copied into the parameter of B. So where does the third one come from?
7 年多之前 回复
csdnceshi68
local-host passing by values is cheap for rvalues but with the same example for lvalues,string s= "value"; B(s); will do 3 copies. So I guess best solution would be to have two overloads, one for lvalue ref and other for rvalue ref.
7 年多之前 回复
weixin_41568131
10.24 : Sure, but your original question was "that's still more expensive by a constant amount (independent of the length of the string being moved) right?" The point I'm trying to make is, it could be more expensive by different constant amounts depending on the length of the string, which gets summed up as "no".
8 年多之前 回复
csdnceshi64
游.程 In order analysis, if the worst case of something is bound by a constant, then it's still constant time. Is there not a longest small string? Doesn't that string take some constant amount of time to copy? Don't all smaller strings take less time to copy? Then, string copying for small strings is "constant time" in order analysis — despite small strings taking varying amounts of time to copy. Order analysis is concerned with asymptotic behaviour.
8 年多之前 回复
weixin_41568131
10.24 : Do you understand what "implementation-dependent" means? What you're saying is wrong, because it depends on if and how SSO is implemented.
8 年多之前 回复
csdnceshi64
游.程 Do you know how order analysis works? I'm saying that it's O(1) to move a string. It doesn't depend on small strings.
8 年多之前 回复
weixin_41568131
10.24 : It could matter because the data inside the small-string buffer could be copied conditionally. This just goes back to "it depends".
8 年多之前 回复
csdnceshi64
游.程 it doesn't matter because the buffer still has a fixed maximum size.
8 年多之前 回复
weixin_41568131
10.24 : I doubt the implementation would copy the contents of the unused small-string buffer if the actual string data length exceeds that buffer size.
8 年多之前 回复
csdnceshi64
游.程 It shouldn't have anything to do with small strings since there must be some constant k such that all small strings can be copied in less k time.
8 年多之前 回复
weixin_41568131
10.24 : That depends whether the standard library implementation uses small-string-optimization, and if so, what that small-string buffer size is.
8 年多之前 回复
csdnceshi64
游.程 When you say that moving into a value is more expensive than using references, that's still more expensive by a constant amount (independent of the length of the string being moved) right?
8 年多之前 回复

Are the days of passing const std::string & as a parameter over?

No. Many people take this advice (including Dave Abrahams) beyond the domain it applies to, and simplify it to apply to all std::string parameters -- Always passing std::string by value is not a "best practice" for any and all arbitrary parameters and applications because the optimizations these talks/articles focus on apply only to a restricted set of cases.

If you're returning a value, mutating the parameter, or taking the value, then passing by value could save expensive copying and offer syntactical convenience.

As ever, passing by const reference saves much copying when you don't need a copy.

Now to the specific example:

However inval is still quite a lot larger than the size of a reference (which is usually implemented as a pointer). This is because a std::string has various components including a pointer into the heap and a member char[] for short string optimization. So it seems to me that passing by reference is still a good idea. Can anyone explain why Herb might have said this?

If stack size is a concern (and assuming this is not inlined/optimized), return_val + inval > return_val -- IOW, peak stack usage can be reduced by passing by value here (note: oversimplification of ABIs). Meanwhile, passing by const reference can disable the optimizations. The primary reason here is not to avoid stack growth, but to ensure the optimization can be performed where it is applicable.

The days of passing by const reference aren't over -- the rules just more complicated than they once were. If performance is important, you'll be wise to consider how you pass these types, based on the details you use in your implementations.

weixin_41568131
10.24 On stack usage, typical ABIs will pass a single reference in a register with no stack usage.
5 年多之前 回复

I've copy/pasted the answer from this question here, and changed the names and spelling to fit this question.

Here is code to measure what is being asked:

#include <iostream>

struct string
{
    string() {}
    string(const string&) {std::cout << "string(const string&)\n";}
    string& operator=(const string&) {std::cout << "string& operator=(const string&)\n";return *this;}
#if (__has_feature(cxx_rvalue_references))
    string(string&&) {std::cout << "string(string&&)\n";}
    string& operator=(string&&) {std::cout << "string& operator=(string&&)\n";return *this;}
#endif

};

#if PROCESS == 1

string
do_something(string inval)
{
    // do stuff
    return inval;
}

#elif PROCESS == 2

string
do_something(const string& inval)
{
    string return_val = inval;
    // do stuff
    return return_val; 
}

#if (__has_feature(cxx_rvalue_references))

string
do_something(string&& inval)
{
    // do stuff
    return std::move(inval);
}

#endif

#endif

string source() {return string();}

int main()
{
    std::cout << "do_something with lvalue:\n\n";
    string x;
    string t = do_something(x);
#if (__has_feature(cxx_rvalue_references))
    std::cout << "\ndo_something with xvalue:\n\n";
    string u = do_something(std::move(x));
#endif
    std::cout << "\ndo_something with prvalue:\n\n";
    string v = do_something(source());
}

For me this outputs:

$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=1 test.cpp
$ a.out
do_something with lvalue:

string(const string&)
string(string&&)

do_something with xvalue:

string(string&&)
string(string&&)

do_something with prvalue:

string(string&&)
$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=2 test.cpp
$ a.out
do_something with lvalue:

string(const string&)

do_something with xvalue:

string(string&&)

do_something with prvalue:

string(string&&)

The table below summarizes my results (using clang -std=c++11). The first number is the number of copy constructions and the second number is the number of move constructions:

+----+--------+--------+---------+
|    | lvalue | xvalue | prvalue |
+----+--------+--------+---------+
| p1 |  1/1   |  0/2   |   0/1   |
+----+--------+--------+---------+
| p2 |  1/0   |  0/1   |   0/1   |
+----+--------+--------+---------+

The pass-by-value solution requires only one overload but costs an extra move construction when passing lvalues and xvalues. This may or may not be acceptable for any given situation. Both solutions have advantages and disadvantages.

csdnceshi51
旧行李 Did you get different results? If so, using what compiler with what command line arguments?
接近 6 年之前 回复
csdnceshi55
~Onlooker You should probably optimize the code before performing the tests…
接近 6 年之前 回复
csdnceshi51
旧行李 This answer counts the number of moves and copies a std::string will undergo under the pass-by-value design described by both Herb and Dave, vs passing by reference with a pair of overloaded functions. I use the OP's code in the demo, except for substituting in a dummy string to shout-out when it is getting copied/moved.
8 年多之前 回复
weixin_41568196
撒拉嘿哟木头 std::string is a standard library class. It already is both moveable and copyable. I don't see how this is relevant. The OP is asking more about the performance of move vs. references, not the performance of move vs. copy.
8 年多之前 回复

IMO using the C++ reference for std::string is a quick and short local optimization, while using passing by value could be (or not) a better global optimization.

So the answer is: it depends on circumstances:

  1. If you write all the code from the outside to the inside functions, you know what the code does, you can use the reference const std::string &.
  2. If you write the library code or use heavily library code where strings are passed, you likely gain more in global sense by trusting std::string copy constructor behavior.

Herb Sutter is still on record, along with Bjarne Stroustroup, in recommending const std::string& as a parameter type; see https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-in .

There is a pitfall not mentioned in any of the other answers here: if you pass a string literal to a const std::string& parameter, it will pass a reference to a temporary string, created on-the-fly to hold the characters of the literal. If you then save that reference, it will be invalid once the temporary string is deallocated. To be safe, you must save a copy, not the reference. The problem stems from the fact that string literals are const char[N] types, requiring promotion to std::string.

The code below illustrates the pitfall and the workaround, along with a minor efficiency option -- overloading with a const char* method, as described at Is there a way to pass a string literal as reference in C++.

(Note: Sutter & Stroustroup advise that if you keep a copy of the string, also provide an overloaded function with a && parameter and std::move() it.)

#include <string>
#include <iostream>
class WidgetBadRef {
public:
    WidgetBadRef(const std::string& s) : myStrRef(s)  // copy the reference...
    {}

    const std::string& myStrRef;    // might be a reference to a temporary (oops!)
};

class WidgetSafeCopy {
public:
    WidgetSafeCopy(const std::string& s) : myStrCopy(s)
            // constructor for string references; copy the string
    {std::cout << "const std::string& constructor\n";}

    WidgetSafeCopy(const char* cs) : myStrCopy(cs)
            // constructor for string literals (and char arrays);
            // for minor efficiency only;
            // create the std::string directly from the chars
    {std::cout << "const char * constructor\n";}

    const std::string myStrCopy;    // save a copy, not a reference!
};

int main() {
    WidgetBadRef w1("First string");
    WidgetSafeCopy w2("Second string"); // uses the const char* constructor, no temp string
    WidgetSafeCopy w3(w2.myStrCopy);    // uses the String reference constructor
    std::cout << w1.myStrRef << "\n";   // garbage out
    std::cout << w2.myStrCopy << "\n";  // OK
    std::cout << w3.myStrCopy << "\n";  // OK
}

OUTPUT:

const char * constructor
const std::string& constructor

Second string
Second string
csdnceshi53
Lotus@ You're welcome. It's an easy mix-up to make, it can sometimes be confusing whether any given T&& is a deduced universal reference or a non-deduced rvalue reference; it probably would've been clearer if they introduced a different symbol for universal references (such as &&&, as a combination of & and &&), but that would probably just look silly.
3 年多之前 回复
csdnceshi55
~Onlooker thank you; I removed the incorrect final sentence claiming, in effect, that std::string&& would be a universal reference.
3 年多之前 回复
csdnceshi53
Lotus@ Note that T&& isn't always a universal reference; in fact, std::string&& will always be an rvalue reference, and never a universal reference, because no type deduction is performed. Thus, Stroustroup & Sutter's advice doesn't conflict with Meyers'.
3 年多之前 回复
csdnceshi59
ℙℕℤℝ This is a different issue and WidgetBadRef doesn't need to have a const& parameter to go wrong. The question is if WidgetSafeCopy just took a string parameter would it be slower? (I think the copy temporary to member is certainly easier to spot)
3 年多之前 回复

This highly depends on the compiler's implementation.

However, it also depends on what you use.

Lets consider next functions :

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

These functions are implemented in a separate compilation unit in order to avoid inlining. Then :
1. If you pass a literal to these two functions, you will not see much difference in performances. In both cases, a string object has to be created
2. If you pass another std::string object, foo2 will outperform foo1, because foo1 will do a deep copy.

On my PC, using g++ 4.6.1, I got these results :

  • variable by reference: 1000000000 iterations -> time elapsed: 2.25912 sec
  • variable by value: 1000000000 iterations -> time elapsed: 27.2259 sec
  • literal by reference: 100000000 iterations -> time elapsed: 9.10319 sec
  • literal by value: 100000000 iterations -> time elapsed: 8.62659 sec
csdnceshi80
胖鸭 or he preferred to wait 9 seconds instead of 90.
大约 2 年之前 回复
csdnceshi62
csdnceshi62 Might be a typo, but the last two literal timings have 10x fewer loops.
3 年多之前 回复
weixin_41568126
乱世@小熊 To me "it depends on what you do inside the function" is bad design - since you are basing the signature of the function on the internals of the implementation.
接近 4 年之前 回复
weixin_41568184
叼花硬汉 I used -O3, but the similar results are for other settings.
8 年多之前 回复
csdnceshi60
℡Wang Yan using g++ 4.6.1 - what optimization settings?
8 年多之前 回复
csdnceshi58
Didn"t forge That's not my point. Whether passing by value or by reference is better depends on what you're doing inside the function. In your example, you're not actually using much of the string object, so reference is obviously better. But if the function's task were to place the string in some struct or to perform, say, some recursive algorithm involving multiple splits of the string, passing by value might actually save some copying, compared to passing by reference. Nicol Bolas explains it quite well.
8 年多之前 回复
weixin_41568184
叼花硬汉 Yes, off course. My assumption that both functions are doing exactly the same thing.
8 年多之前 回复
weixin_41568184
叼花硬汉 Yes, I meant lvalue std::string
8 年多之前 回复
csdnceshi58
Didn"t forge What's more relevant is what's happening inside the function: would it, if called with a reference, need to make a copy internally that can be omitted when passing by value?
8 年多之前 回复
csdnceshi67
bug^君 Of course for 2. you specifically mean a string lvalue. If the function primarily handles tempories, returned from other functions for example then it'll be a different story.
8 年多之前 回复

Almost.

In C++17, we have basic_string_view<?>, which brings us down to basically one narrow use case for std::string const& parameters.

The existence of move semantics has eliminated one use case for std::string const& -- if you are planning on storing the parameter, taking a std::string by value is more optimal, as you can move out of the parameter.

If someone called your function with a raw C "string" this means only one std::string buffer is ever allocated, as opposed to two in the std::string const& case.

However, if you don't intend to make a copy, taking by std::string const& is still useful in C++14.

With std::string_view, so long as you aren't passing said string to an API that expects C-style '\0'-terminated character buffers, you can more efficiently get std::string like functionality without risking any allocation. A raw C string can even be turned into a std::string_view without any allocation or character copying.

At that point, the use for std::string const& is when you aren't copying the data wholesale, and are going to pass it on to a C-style API that expects a null terminated buffer, and you need the higher level string functions that std::string provides. In practice, this is a rare set of requirements.

weixin_41568174
from.. It is a YMMV thing – but it’s a frequent use-case if (say) you are programming against POSIX, or other APIs where C-string semantics are, like, the lowest common denominator. I should say really that I love std::string_view – as you point out, “A raw C string can even be turned into a std::string_view without any allocation or character copying” which is something worth remembering to those who are using C++ in the context of such API usage, indeed.
大约 2 年之前 回复
csdnceshi52
妄徒之命 To be clear, for std::string to dominate you don't just need some of those requirements but all of them. Any one or even two of those is, I'd admit, common. Maybe you do commonly need all 3 (like, you are doing some parsing of a string argument to pick which C API you are going to pass it wholesale to?)
大约 2 年之前 回复
weixin_41568174
from.. I appreciate this answer – but I do want to point out it that it does suffer (as many quality answers do) from a bit of domain-specific bias. To wit: “In practice, this is a rare set of requirements”… in my own development experience, these constraints – which seem abnormally narrow to the author – are met, like, literally all the time. It’s worth pointing this out.
大约 2 年之前 回复
csdnceshi56
lrony* Very interesting addition, thank you.
5 年多之前 回复

See “Herb Sutter "Back to the Basics! Essentials of Modern C++ Style”. Among other topics, he reviews the parameter passing advice that’s been given in the past, and new ideas that come in with C++11 and specifically looks at the idea of passing strings by value.

slide 24

The benchmarks show that passing std::strings by value, in cases where the function will copy it in anyway, can be significantly slower!

This is because you are forcing it to always make a full copy (and then move into place), while the const& version will update the old string which may reuse the already-allocated buffer.

See his slide 27: For “set” functions, option 1 is the same as it always was. Option 2 adds an overload for rvalue reference, but this gives a combinatorial explosion if there are multiple parameters.

It is only for “sink” parameters where a string must be created (not have its existing value changed) that the pass-by-value trick is valid. That is, constructors in which the parameter directly initializes the member of the matching type.

If you want to see how deep you can go in worrying about this, watch Nicolai Josuttis’s presentation and good luck with that (“Perfect — Done!” n times after finding fault with the previous version. Ever been there?)


This is also summarized as ⧺F.15 in the Standard Guidelines.

As @JDługosz points out in the comments, Herb gives other advice in another (later?) talk, see roughly from here: https://youtu.be/xnqTKD8uD64?t=54m50s.

His advice boils down to only using value parameters for a function f that takes so-called sink arguments, assuming you will move construct from these sink arguments.

This general approach only adds the overhead of a move constructor for both lvalue and rvalue arguments compared to an optimal implementation of f tailored to lvalue and rvalue arguments respectively. To see why this is the case, suppose f takes a value parameter, where T is some copy and move constructible type:

void f(T x) {
  T y{std::move(x)};
}

Calling f with an lvalue argument will result in a copy constructor being called to construct x, and a move constructor being called to construct y. On the other hand, calling f with an rvalue argument will cause a move constructor to be called to construct x, and another move constructor to be called to construct y.

In general, the optimal implementation of f for lvalue arguments is as follows:

void f(const T& x) {
  T y{x};
}

In this case, only one copy constructor is called to construct y. The optimal implementation of f for rvalue arguments is, again in general, as follows:

void f(T&& x) {
  T y{std::move(x)};
}

In this case, only one move constructor is called to construct y.

So a sensible compromise is to take a value parameter and have one extra move constructor call for either lvalue or rvalue arguments with respect to the optimal implementation, which is also the advice given in Herb's talk.

As @JDługosz pointed out in the comments, passing by value only makes sense for functions that will construct some object from the sink argument. When you have a function f that copies its argument, the pass-by-value approach will have more overhead than a general pass-by-const-reference approach. The pass-by-value approach for a function f that retains a copy of its parameter will have the form:

void f(T x) {
  T y{...};
  ...
  y = std::move(x);
}

In this case, there is a copy construction and a move assignment for an lvalue argument, and a move construction and move assignment for an rvalue argument. The most optimal case for an lvalue argument is:

void f(const T& x) {
  T y{...};
  ...
  y = x;
}

This boils down to an assignment only, which is potentially much cheaper than the copy constructor plus move assignment required for the pass-by-value approach. The reason for this is that the assignment might reuse existing allocated memory in y, and therefore prevent (de)allocations, whereas the copy constructor will usually allocate memory.

For an rvalue argument the most optimal implementation for f that retains a copy has the form:

void f(T&& x) {
  T y{...};
  ...
  y = std::move(x);
}

So, only a move assignment in this case. Passing an rvalue to the version of f that takes a const reference only costs an assignment instead of a move assignment. So relatively speaking, the version of f taking a const reference in this case as the general implementation is preferable.

So in general, for the most optimal implementation, you will need to overload or do some kind of perfect forwarding as shown in the talk. The drawback is a combinatorial explosion in the number of overloads required, depending on the number of parameters for f in case you opt to overload on the value category of the argument. Perfect forwarding has the drawback that f becomes a template function, which prevents making it virtual, and results in significantly more complex code if you want to get it 100% right (see the talk for the gory details).

csdnceshi50
三生石@ that figure and advice is in the Standard Guidelines document, now.
2 年多之前 回复
csdnceshi53
Lotus@ thanks for the pointer to Herb's talk, I just watched it and completely revised my answer. I was not aware of the (move) assign advice.
2 年多之前 回复
csdnceshi50
三生石@ See Herb Sutter’s findings in my new answer: only do that when you move construct, not move assign.
2 年多之前 回复

The problem is that "const" is a non-granular qualifier. What is usually meant by "const string ref" is "don't modify this string", not "don't modify the reference count". There is simply no way, in C++, to say which members are "const". They either all are, or none of them are.

In order to hack around this language issue, STL could allow "C()" in your example to make a move-semantic copy anyway, and dutifully ignore the "const" with regard to the reference count (mutable). As long as it was well-specified, this would be fine.

Since STL doesn't, I have a version of a string that const_casts<> away the reference counter (no way to retroactively make something mutable in a class hierarchy), and - lo and behold - you can freely pass cmstring's as const references, and make copies of them in deep functions, all day long, with no leaks or issues.

Since C++ offers no "derived class const granularity" here, writing up a good specification and making a shiny new "const movable string" (cmstring) object is the best solution I've seen.

csdnceshi80
胖鸭 yep... instead of casting away, it should be mutable... but you can't change an STL member to mutable in a derived class.
2 年多之前 回复
weixin_41568196
撒拉嘿哟木头 No way, except for the mutable keyword...
5 年多之前 回复
共11条数据 1 尾页
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问