使用函数返回值初始化std :: string,是否有副本?

时间:2022-09-10 23:47:47

I have the following code, I am using GCC and C++11:

我有以下代码,我正在使用GCC和C ++ 11:

std::string system_call(const char *cmd){
   std::string a;
   ...
   return a;
}

std::string st = system_call("whatever code");

Is there an implicit copy there? I am calling this function many times, and I guess it is doing a copy from the return value of the system_call to the variable st, and later on releasing the temporary r-value.

那里有隐含的副本吗?我多次调用这个函数,我猜它是从system_call的返回值到变量st进行复制,然后释放临时r值。

Is there any way I can avoid the copy? Using st.swap(system_call()) throws and error in the compiler:

有什么方法可以避免副本吗?在编译器中使用st.swap(system_call())抛出并出错:

error: no matching function for call to 'std::basic_string::swap(std::string)'

错误:没有匹配函数来调用'std :: basic_string :: swap(std :: string)'

My questions are:

我的问题是:

  1. If there is a problem or not
  2. 如果有问题

  3. How to avoid it, if there is one
  4. 如果有的话,如何避免它

Thanks

EDIT: Found a way to make the explicit swap work. But the answers where right, there is no benefit since the compiler was already replacing st with the return value, without any copy.

编辑:找到一种方法来进行显式交换工作。但答案正确,没有任何好处,因为编译器已经用返回值替换st,没有任何副本。

system_call("whatever").swap(st);

3 个解决方案

#1


12  

In general, if the std::string is constructed in the call and then returned most modern compilers with apply the return value optimization (a special case of copy elision). Your case in particular is the Named RVO (thanks @NathanOliver for pointing this out), if you want to look up the precise rules. From C++17 on this optimization is guaranteed through the standard.

通常,如果在调用中构造了std :: string,然后返回大多数现代编译器并应用返回值优化(复制省略的特殊情况)。你的案例特别是命名的RVO(感谢@NathanOliver指出这一点),如果你想查找精确的规则。从C ++ 17开始,这种优化通过标准得到保证。

Whether or not the optimization is applied is hard to tell, however if it is not, then in C++11 and above it is very likely that the std::string object in the scope of the function will be moved from and that the return value will be moved to. You could theoretically call std::move on the return value, but thereby you would also prevent any RVO from happening. While move may be efficient, RVO typically yields faster code, because nothing is moved or copied at all, but the object is constructed in place, so I would advise against doing it, and in favor of relying on your compiler.

是否应用优化很难说,但是如果不是,那么在C ++ 11及更高版本中,很可能是函数范围内的std :: string对象将被移动并且返回值将被移动到。理论上你可以在返回值上调用std :: move,但是你也可以防止任何RVO发生。虽然移动可能是有效的,但RVO通常会产生更快的代码,因为根本没有移动或复制任何内容,但是对象是在适当的位置构建的,所以我建议不要这样做,并且赞成依赖于编译器。

#2


4  

Elision is the permission the standard gives to compilers to make multiple values share existence, when they seem to be different values in the code.

Elision是标准赋予编译器允许多个值共享存在的权限,当它们在代码中看起来是不同的值时。

std::string system_call(const char *cmd){
  std::string a;
  ...
  return a; // all return paths return `a` directly
}

std::string st = system_call("whatever code");

In the above case, elision means that a, the return value of system_call and st are all the same object.

在上面的例子中,elision意味着a,system_call和st的返回值都是同一个对象。

Modern compilers elide unless you give them a pathological flag to say "don't elide", whenever the standard and the code permits it. "What if it doesn't elide when possible" is like asking what if the compiler implements integer addition as a looped increment.

现代编制者除非你给他们一个病态的标志,说“不要忽视”,只要标准和守则允许它。 “如果它在可能的情况下不会消失,那么”就像询问编译器如何将整数加法实现为循环增量一样。

Both are permitted by the standard, neither are reasonable to expect.

两者都是标准允许的,也不合理。

When elision fails (because your code makes it impossible), it falls back on move semantics in C++11. For std::string that means no memory allocation occurs on the move; in the case of a small string optimization some small number of characters may be copied.

当elision失败时(因为你的代码使其无法实现),它会回归到C ++ 11中的移动语义。对于std :: string,这意味着移动时不会发生内存分配;在小字符串优化的情况下,可以复制少量字符。

Elision can fail if your code could return a given named variable along one path, and a different one or a temporary along another path. Or if you return a function parameter.

如果您的代码可以沿一条路径返回给定的命名变量,而另一条路径返回另一条路径,则Elision可能会失败。或者,如果您返回一个函数参数。

Elision is permitted if your return statement is return named_variable; or return some_temporary_object;, where the returned object matches the type of the function return. It is also permitted when you do some_type bob = some_temporary;, where some_temporary is a temporary object of type some_type. Similarly you can elide into a function argument (but not out of it).

如果return语句返回named_variable,则允许Elision;或返回some_temporary_object;,其中返回的对象与函数返回的类型匹配。当你执行some_type bob = some_temporary;时也允许它,其中some_temporary是some_type类型的临时对象。类似地,你可以忽略一个函数参数(但不是它)。

There is no way to "guarantee" elision.

没有办法“保证”省略。

C++17 makes almost all of the elide-from-temporary cases above mandatory.

C ++ 17使得几乎所有从临时案件中删除的情况都是强制性的。

For elision to work in C++14 and before, there must be a copy or move constructor. When elision occurs, it is not called, but it must exist. In C++17 when temporaries are "force" elided, no copy or move constructor need exist: the temporary is not a distinct object, but rather a clause that represents "how to construct an object", which is only done later.

要使elision在C ++ 14及之前工作,必须有一个复制或移动构造函数。当省略时,不会调用它,但它必须存在。在C ++ 17中,当temporaries被“强制”省略时,不需要存在复制或移动构造函数:临时不是一个独特的对象,而是一个表示“如何构造一个对象”的子句,这只能在以后完成。

#3


3  

Since the question is tagged as c++11, I assume you are compiling the code with a c++11 compiler.

由于问题被标记为c ++ 11,我假设您正在使用c ++ 11编译器编译代码。

In this case, the value returned by system_call won't be copied. The compiler might move construct st with the return value, or use Return Value Optimization to erase the copy. Either way, there won't be a copy.

在这种情况下,不会复制system_call返回的值。编译器可能会使用返回值移动构造st,或使用返回值优化来擦除副本。无论哪种方式,都不会有副本。

#1


12  

In general, if the std::string is constructed in the call and then returned most modern compilers with apply the return value optimization (a special case of copy elision). Your case in particular is the Named RVO (thanks @NathanOliver for pointing this out), if you want to look up the precise rules. From C++17 on this optimization is guaranteed through the standard.

通常,如果在调用中构造了std :: string,然后返回大多数现代编译器并应用返回值优化(复制省略的特殊情况)。你的案例特别是命名的RVO(感谢@NathanOliver指出这一点),如果你想查找精确的规则。从C ++ 17开始,这种优化通过标准得到保证。

Whether or not the optimization is applied is hard to tell, however if it is not, then in C++11 and above it is very likely that the std::string object in the scope of the function will be moved from and that the return value will be moved to. You could theoretically call std::move on the return value, but thereby you would also prevent any RVO from happening. While move may be efficient, RVO typically yields faster code, because nothing is moved or copied at all, but the object is constructed in place, so I would advise against doing it, and in favor of relying on your compiler.

是否应用优化很难说,但是如果不是,那么在C ++ 11及更高版本中,很可能是函数范围内的std :: string对象将被移动并且返回值将被移动到。理论上你可以在返回值上调用std :: move,但是你也可以防止任何RVO发生。虽然移动可能是有效的,但RVO通常会产生更快的代码,因为根本没有移动或复制任何内容,但是对象是在适当的位置构建的,所以我建议不要这样做,并且赞成依赖于编译器。

#2


4  

Elision is the permission the standard gives to compilers to make multiple values share existence, when they seem to be different values in the code.

Elision是标准赋予编译器允许多个值共享存在的权限,当它们在代码中看起来是不同的值时。

std::string system_call(const char *cmd){
  std::string a;
  ...
  return a; // all return paths return `a` directly
}

std::string st = system_call("whatever code");

In the above case, elision means that a, the return value of system_call and st are all the same object.

在上面的例子中,elision意味着a,system_call和st的返回值都是同一个对象。

Modern compilers elide unless you give them a pathological flag to say "don't elide", whenever the standard and the code permits it. "What if it doesn't elide when possible" is like asking what if the compiler implements integer addition as a looped increment.

现代编制者除非你给他们一个病态的标志,说“不要忽视”,只要标准和守则允许它。 “如果它在可能的情况下不会消失,那么”就像询问编译器如何将整数加法实现为循环增量一样。

Both are permitted by the standard, neither are reasonable to expect.

两者都是标准允许的,也不合理。

When elision fails (because your code makes it impossible), it falls back on move semantics in C++11. For std::string that means no memory allocation occurs on the move; in the case of a small string optimization some small number of characters may be copied.

当elision失败时(因为你的代码使其无法实现),它会回归到C ++ 11中的移动语义。对于std :: string,这意味着移动时不会发生内存分配;在小字符串优化的情况下,可以复制少量字符。

Elision can fail if your code could return a given named variable along one path, and a different one or a temporary along another path. Or if you return a function parameter.

如果您的代码可以沿一条路径返回给定的命名变量,而另一条路径返回另一条路径,则Elision可能会失败。或者,如果您返回一个函数参数。

Elision is permitted if your return statement is return named_variable; or return some_temporary_object;, where the returned object matches the type of the function return. It is also permitted when you do some_type bob = some_temporary;, where some_temporary is a temporary object of type some_type. Similarly you can elide into a function argument (but not out of it).

如果return语句返回named_variable,则允许Elision;或返回some_temporary_object;,其中返回的对象与函数返回的类型匹配。当你执行some_type bob = some_temporary;时也允许它,其中some_temporary是some_type类型的临时对象。类似地,你可以忽略一个函数参数(但不是它)。

There is no way to "guarantee" elision.

没有办法“保证”省略。

C++17 makes almost all of the elide-from-temporary cases above mandatory.

C ++ 17使得几乎所有从临时案件中删除的情况都是强制性的。

For elision to work in C++14 and before, there must be a copy or move constructor. When elision occurs, it is not called, but it must exist. In C++17 when temporaries are "force" elided, no copy or move constructor need exist: the temporary is not a distinct object, but rather a clause that represents "how to construct an object", which is only done later.

要使elision在C ++ 14及之前工作,必须有一个复制或移动构造函数。当省略时,不会调用它,但它必须存在。在C ++ 17中,当temporaries被“强制”省略时,不需要存在复制或移动构造函数:临时不是一个独特的对象,而是一个表示“如何构造一个对象”的子句,这只能在以后完成。

#3


3  

Since the question is tagged as c++11, I assume you are compiling the code with a c++11 compiler.

由于问题被标记为c ++ 11,我假设您正在使用c ++ 11编译器编译代码。

In this case, the value returned by system_call won't be copied. The compiler might move construct st with the return value, or use Return Value Optimization to erase the copy. Either way, there won't be a copy.

在这种情况下,不会复制system_call返回的值。编译器可能会使用返回值移动构造st,或使用返回值优化来擦除副本。无论哪种方式,都不会有副本。