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

bla*_*edo 29 c++ string substring lvalue

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

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

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

编译器对此没有任何问题,并且赋值语句似乎根据打印出的内容没有任何效果.相反,

s.front() = 'x';
Run Code Online (Sandbox Code Playgroud)

具有我期望的效果(因为front返回对字符的引用)改变底层字符串,和

s.length() = 4;
Run Code Online (Sandbox Code Playgroud)

也有产生编译器错误的预期效果,抱怨你不能分配给不是左值的东西,因为length返回一个整数.(好吧,size_t无论如何.)

那么......为什么编译器不会抱怨分配给substr调用的结果呢?它返回一个字符串值,而不是引用,所以它不应该是可赋值的,对吧?但我在g++(6.2.1)和clang++(3.9.0)中尝试了这个,所以它似乎不是一个bug,它似乎也不敏感C++版本(试过03,11,14) ).

M.M*_*M.M 45

结果substr()是一个std::string临时对象 - 它是子串的自包含副本,而不是原始字符串上的视图.

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

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

  1. 返回一个const对象.
  2. 在赋值运算符上使用lvalue ref-qualifier.

选项1会导致代码出现编译错误,但它也会限制一些有效的用例(例如,move退出返回值 - 不能移出const字符串).

选项2防止使用赋值运算符,除非左侧是左值.这是一个好主意恕我直言虽然并非所有人都同意; 看到这个帖子进行讨论.

在任何情况下; 当在C++ 11中添加ref-qualifiers时,建议返回并更改C++ 03中所有容器的规范,但是这个提议未被接受(可能是因为它破坏了现有代码).

std::string是在20世纪90年代设计的,并且做了一些设计选择,事后看来似乎很差,但我们坚持使用它.您必须自己理解问题std::string,并且可能通过使用ref-qualifiers,或者视图等来避免在自己的类中使用它.


dan*_*m64 12

您的代码编译的原因是因为它是合法的C++.这是一个解释发生了什么的链接.

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

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

非类rvalues不可修改,也不能具有cv限定类型(cv资格被忽略).相反,类rvalues是可修改的,可用于通过其成员函数修改对象.他们也可以拥有符合cv标准的类型.

因此,您无法分配给返回的rvalue的原因std::string::length是因为它不是类的实例,并且您可以分配给返回的rvalue的原因std::string::substr是因为它是类的实例.

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


Kir*_*lev 7

看看代码:

s.substr(2,3) = "foo";
Run Code Online (Sandbox Code Playgroud)

函数调用substr返回一个字符串,即一个对象和一个临时值.之后,您修改此对象(实际上是通过从std::string类中调用重载的赋值运算符).不以任何方式保存此临时对象.编译器只是破坏了这个修改过的临时文件.

这段代码没有意义.您可能会问,为什么编译器没有发出警告?答案是编译器可能更好.编译器是由人而不是神创作的.不幸的是,C++允许大量编写无意义的代码或代码来触发未定义的行为.这是该语言的一个方面.与许多其他语言相比,它需要程序员更好的知识和更高的关注度.

我刚刚查看了MSVC 2015,代码:

std::string s1 = "abcdef";
s1.substr(1, 2) = "5678";
Run Code Online (Sandbox Code Playgroud)

编译好.

  • @MarkRansom - 这是内置类型和用户定义类型之间的区别.用户定义类型的操作可能会产生明显的副作用.对rvalues的成员函数调用一直是合法的. (2认同)