【问题标题】:is returning a const std::string really slower than non-const?返回一个 const std::string 真的比非 const 慢吗?
【发布时间】:2019-06-12 21:15:21
【问题描述】:

在另一个问题中,一位用户评论说返回 const std::string 会降低移动构造效率并且速度较慢。

是不是真的给这个方法赋值一串return:

const std::string toJson(const std::string &someText);

const std::string jsonString = toJson(someText);

... 确实比非常量版本慢:

std::string toJson(const std::string &str);

std::string jsonString = toJson(someText);

在这种情况下,移动构造效率是什么意思?

我以前从未听说过这种限制,也不记得在分析器中看到过这种限制。但我很好奇。

编辑:有一个建议的问题:What is move semantics?。虽然其中一些解释当然与效率有关,但它解释了 什么 移动语义的含义,但没有解决 为什么 返回 const 值会对性能产生负面影响。

【问题讨论】:

  • 你质疑它失去了移动语义,还是质疑它更慢?还是两者兼而有之?
  • 看看生成的代码看看。或者使用随机字符串运行几百万次并测量。
  • 另外,是否返回 const 值并不重要。调用者将结果分配给重要的是什么。
  • 返回 const 值是没有意义的。只需按值返回。
  • 返回一个 const value(相对于引用)从不 是有意义的。不要这样做。你的编译器甚至应该警告你。

标签: c++ c++11 c++14


【解决方案1】:

考虑以下函数:

std::string f();
std::string const g();

没有区别:

std::string s1 = f();
std::string s2 = g();

我们现在已经保证了复制省略,在这两种情况下,我们都直接构建到结果对象中。没有复制,没有移动。

但是,两者之间有很大的不同:

std::string s3, s4;
s3 = f(); // this is move assignment
s4 = g(); // this is copy assignment

g() 可能是一个右值,但它是一个 const 右值。它无法绑定到移动赋值运算符采用的string&& 参数,因此我们回退到copy 赋值运算符,其string const& 参数可以愉快地接受右值。

对于像 string 这样的类型,复制肯定比移动慢,其中移动是恒定时间,而复制是线性的,可能需要分配。

不要返回 const 值。


除此之外,对于非类类型:

int f();
int const g();

这两个其实是一样的,都返回int。您不能返回非类类型的 const prvalue 但您可以返回类类型的 const prvalue,这是语言的一个奇怪的怪癖。更容易假装你也不能做后者,因为你不应该这样做。

【讨论】:

  • 另外,auto&& str = g()= f() 不同。
  • @Nikos 复制省略不受 cv 资格差异的影响。
  • 你确定吗?我无法想象一个编译器足够勇敢(C++ 17 之前)复制省略内部 const 但外部非const 的内容。
【解决方案2】:

不阅读规范或其他任何东西,如果我们只是从逻辑上考虑......

例如,假设你有

// Declare the function
std::string const my_function();

// Initialize a non-constant variable using the function
std::string my_string = my_function();

函数返回的值可以复制到一个临时对象,然后函数内部的值被破坏。然后将临时对象(即常量)复制到my_string 对象,然后将临时对象销毁。两个副本和两个破坏。听起来有点过分,你不觉得吗?特别是考虑到函数内的值 临时对象都将被破坏,因此它们实际上不需要保留其内容。

如果可以省略复制不是更好吗,也许两者都可以?然后可能发生的情况是函数内部的值被直接移动到my_string 对象中。任何东西的const 状态都无关紧要,因为被移动的对象接下来将被销毁。

后者是现代编译器所做的,即使函数被声明为返回 const 值,它们也会移动。即使函数内部的值或对象也是const

【讨论】:

  • 关于你的最后一段,你能分享更多的信息吗?听起来……很奇怪。
【解决方案3】:

这样的语句在初始化方面有一定的意义,

std::string getString();
const std::string getConstantString();

std::string str = getString(); // 1
const std::string str = getConstantString(); //2

12 两个初始化语句都属于复制初始化。现在它取决于return typecv-qualification(常量和易失性),有两种可能性,如果return typecv-unqualifiedmove constructor 可用于类,那么对象将是move initialized,如语句1 , 如果 return typecv-qualified 那么对象将是 copy initialized 就像在声明 2 中一样。
但是有一个称为copy-elision(忽略cv-qualification)的优化,由于copy-elision对象直接构造到存储中,否则它们将被复制/移动到。

copy-elision有两种,NRVO, "named return value optimization"RVO, "return value optimization",但是从c++17返回值优化是强制性的,不再被认为是复制省略。
请参阅以下链接copy-elision 了解更多详情。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-10
    • 1970-01-01
    • 1970-01-01
    • 2018-06-01
    • 1970-01-01
    • 2011-01-07
    相关资源
    最近更新 更多