为什么分配给 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
对象,它有一个赋值运算符函数,您的代码调用该函数来修改临时对象。
这有点令人惊讶 -- 修改临时对象并丢弃结果通常表示逻辑错误,因此通常人们尝试通过两种方式来改善这种情况:
- Return 一个
const
对象。
- 在赋值运算符上使用左值引用限定符。
选项 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 或视图或其他任何方式在您自己的 类 中避免它。
我刚刚发现了一个最令人困惑的错误,我不明白为什么编译器没有为我标记它。如果我写以下内容:
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
对象,它有一个赋值运算符函数,您的代码调用该函数来修改临时对象。
这有点令人惊讶 -- 修改临时对象并丢弃结果通常表示逻辑错误,因此通常人们尝试通过两种方式来改善这种情况:
- Return 一个
const
对象。 - 在赋值运算符上使用左值引用限定符。
选项 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 或视图或其他任何方式在您自己的 类 中避免它。