为什么分配给 substr 调用的结果不是编译器错误?

Why is it not a compiler error to assign to the result of a substr call?

我刚刚发现了一个最令人困惑的错误,我不明白为什么编译器没有为我标记它。如果我写以下内容:

string s = "abcdefghijkl";
cout << s << endl;

s.substr(2,3) = "foo";
s.substr(8,1) = '.';
s.substr(9,1) = 4;
cout << s << endl;

编译器对此没有任何问题,根据打印出来的内容,赋值语句似乎没有效果。相比之下,

s.front() = 'x';

具有我期望的效果(因为 front returns 对字符的引用)更改基础字符串,并且

s.length() = 4;

还具有生成编译器错误的预期效果,抱怨您不能分配给不是左值的东西,因为 length returns 是一个整数。 (嗯,无论如何 size_t。)

那么...为什么编译器不抱怨分配给 substr 调用的结果?它 returns 是一个字符串值,而不是一个引用,所以它不应该是可分配的,对吧?但是我已经在g++(6.2.1)和clang++(3.9.0)中尝试过,所以它似乎不是一个错误,而且它似乎也不敏感到 C++ 版本(试过 03、11、14)。

看代码:

s.substr(2,3) = "foo";

函数调用substr returns一个字符串,即一个对象和一个临时值。之后修改此对象(实际上是通过从 std::string class 调用重载赋值运算符)。这个临时对象不会以任何方式保存。编译器简单地销毁这个修改后的临时文件。

这段代码没有意义。你可能会问,为什么编译器不给出警告?答案是编译器可能更好。编译器是人写的,不是神写的。不幸的是,C++ 允许以多种方式编写无意义的代码或触发未定义行为的代码。这是这种语言的方面之一。与许多其他语言相比,它需要程序员更多的知识和更多的关注。

刚刚用MSVC 2015查了一下,代码:

std::string s1 = "abcdef";
s1.substr(1, 2) = "5678";

编译正常。

你的代码编译的原因是因为它是合法的 C++。这是一个 link 来解释发生了什么。

https://accu.org/index.php/journals/227

很长,所以我会引用最相关的部分:

Non-class rvalues are not modifiable, nor can have cv-qualified types (the cv-qualifications are ignored). On the contrary, the class rvalues are modifiable and can be used to modify an object via its member functions. They can also have cv-qualified types.

所以你不能分配给 std::string::length 返回的右值的原因是因为它不是 class 的实例,而你可以分配给从返回的右值的原因std::string::substr 是因为它是 class.

的实例

我不太明白为什么要这样定义语言,但事实就是如此。

substr() 的结果是一个 std::string 临时对象 -- 它是子字符串的独立副本,而不是原始字符串的视图。

作为一个 std::string 对象,它有一个赋值运算符函数,您的代码调用该函数来修改临时对象。

这有点令人惊讶 -- 修改临时对象并丢弃结果通常表示逻辑错误,因此通常人们尝试通过两种方式来改善这种情况:

  1. Return 一个 const 对象。
  2. 在赋值运算符上使用左值引用限定符。

选项 1 会导致您的代码出现编译错误,但它也会限制一些有效的用例(例如 move-ing 超出 return 值——您不能移动来自 const 字符串)。

选项 2 阻止使用赋值运算符,除非左侧是左值。恕我直言,这是个好主意,尽管并非所有人都同意; see this thread for discussion

无论如何;当在 C++11 it was proposed 中添加引用限定符以返回并更改 C++03 中所有容器的规范时,该提议未被接受(大概是因为它破坏了现有代码)。

std::string 是在 1990 年代设计的,做出了一些在今天看来很糟糕的设计选择,但我们坚持了下来。您必须只了解 std::string 本身的问题,并且也许可以通过使用 ref-qualifiers 或视图或其他任何方式在您自己的 类 中避免它。