【问题标题】:Impossible to disable return value optimization for std::string?无法禁用 std::string 的返回值优化?
【发布时间】:2013-11-06 08:30:38
【问题描述】:

鉴于这个最小的例子。

#include <iostream>
#include <string>

void print_ptr(const std::string& s)
{
    const char* data = s.data();
    std::cout << "ptr: " << (void*)data << std::endl;
}

std::string str_return(const char* suffix)
{
    std::string s("prefix");
    s += " ";
    s += suffix;
    print_ptr(s);
    return s;
}

int main()
{
    std::string s = str_return("suffix"), t;
    print_ptr(s);
    t = str_return("suffix2");
    print_ptr(t);
    return 0;
}

我是这样编译的:

g++ -std=c++98 -fno-elide-constructors -g -Wall  str_return.cpp -o str_return

我的 g++:

gcc version 4.7.1

输出:

ptr: 0x804b04c
ptr: 0x804b04c
ptr: 0x804b07c
ptr: 0x804b07c

为什么指针仍然相等?

  • 它不应该是返回值优化 - 我把它关掉了
  • 它不应该是移动构造函数,因为我采用了一个非常古老的 c++ 标准

如何禁用此行为?

【问题讨论】:

  • 有什么理由这样做吗?
  • @JurajBlaho 我只是想确定它不是副本的原因是什么。这样,我就知道快速返回字符串需要什么。
  • 仅供参考:当使用 clang (3.2) 和 gcc libstdc++ 编译时,我在从 str_return() 返回时遇到分段错误。 这强烈暗示了库的错误/非标准行为。使用 llvm libc++ 时,指针是不同的(尽管它在 t 中重用了 s.data(),即第 1 行和第 3 行报告相同的 ptr)。
  • @Walter,这意味着您的 clang 安装失败了,而不是 libstdc++ 中存在错误。 libstdc++ 对其 std::string 使用 Copy-On-Write,它 100% 符合 C++03(本示例使用)
  • 如果您想阻止 Copy-On-Write 共享缓冲区,则显式复制缓冲区:std::string copy(s.data(), s.size()),但在此示例中,这只是一种有害的悲观化,因为在 str_return 之后返回 st 无论如何都不会与另一个对象共享他们的数据。

标签: c++ copy-constructor return-value-optimization


【解决方案1】:

返回值优化会影响本地对象(str_return 函数中的s)。你永远不会使用它。

字符串对象本身管理动态内存,并选择在返回时将该托管内存交给下一个字符串。您正在检测的是 托管内存。很明智,这不会改变。

如果你真的想看到 RVO 的效果,请检测本地对象:

#include <iostream>
#include <string>

void print_ptr(const std::string& s)
{
    std::cout << "ptr: " << static_cast<const void *>(&s) << std::endl;
}

std::string str_return(const char* suffix)
{
    std::string s("prefix");
    s += " ";
    s += suffix;
    print_ptr(s);
    return s;
}

int main()
{
    std::string s = str_return("suffix");
    print_ptr(s);
    std::string t = str_return("suffix2");
    print_ptr(t);
}

【讨论】:

  • 谢谢,我从这个例子中明白了你的意思。所以你认为没有什么可以让我的编译器对 internal 数据进行硬拷贝?
  • @Johannes:如果您愿意,可以强制复制对象。
  • 好的,但是这段代码,没有改变,对于硬拷贝可能总是安全的(不管我将使用什么编译器标志)?
  • @Johannes:它将在 C++11 中。它在 C++98/03 中未指定。
  • 什么是“保护硬拷贝”是什么意思?
【解决方案2】:

您可能没有体验 RVO。观察到的行为可能是由 GCC 中 std::string 的实现中使用的写时复制优化引起的。因此,复制构造函数实际上可能会运行,但分配的缓冲区不会被复制。

【讨论】:

  • 在 C++98 中它不是“非标准”的,在 C++11 中存在产生相同行为的移动语义。
  • @KerrekSB 查看我对原始 Q 的评论。
【解决方案3】:

我无法评论答案,所以我将在此处发布通知: 如果你为你的 string s 调用构造函数,然后你调用 str_return - 地址将不同:

std::string s; // implicit constructor call
print_ptr( s );
s  = str_return( "suffix" );
print_ptr( s );
std::cout << std::endl;

输出将是:

ptr: 0x28fec4  // for the original s;
ptr: 0x28fec8  // for local variable in function str_return
ptr: 0x28fec4  // the address of s after call str_return didn't change

【讨论】:

  • 这没有帮助,对象在您的示例中不包含相同的数据。
  • 没找到你。哪些对象必须包含相同的数据?
  • 默认构造的字符串不包含与str_return 返回的数据相同的数据。无论如何,您显然没有使用 GCC 或其他使用 COW 字符串的实现进行测试,因为如果您使用 COW 字符串,s 确实 持有的字符串的地址会发生变化。您所做的只是表明另一个实现的行为与 GCC 不同,这对使用 GCC 的 OP 没有帮助。
  • 赋值后 s = str_return( "suffix" ); s 将是“前缀后缀”。它是由g++编译的。而且仔细看,将函数str_return的结果赋值给字符串s后,它的地址还是一样的:0x28fec4,就像调用构造函数之后一样。
  • 你在打印什么,字符串对象的地址,或者它的数据?因为使用 G++,赋值后数据会发生变化。
猜你喜欢
  • 1970-01-01
  • 2012-02-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多