提问人:Jason 提问时间:7/24/2023 最后编辑:Jason 更新时间:7/25/2023 访问量:75
这不应该给我悬而未决的参考错误吗?
Shouldn't this give me dangling reference errors?
问:
在使用了大约十年的其他编程语言之后,我将回到 C++,所以请耐心等待。以下程序在 CLion 的 C++20 项目中为我编译:
#include <iostream>
using namespace std;
class MyClass {
private:
public:
MyClass() {
cout << "MyClass constructor" << endl;
}
MyClass(const MyClass& myClass) {
cout << "MyClass copy constructor" << endl;
}
MyClass& operator=(const MyClass& myClass) {
cout << "MyClass operator=" << endl;
return *this;
}
friend std::ostream& operator<< (std::ostream& os, const MyClass &myClass){
return os << "MyClass stringifier";
}
~MyClass(){
cout << "MyClass destructor" << endl;
}
};
MyClass& f(){
cout << "f() entered" << endl;
MyClass x;
return x;
}
MyClass* g(){
cout << "g() entered" << endl;
MyClass x;
return &x;
}
int main() {
cout << "main() entered" << endl;
MyClass& a = f();
cout << "f() exited" << endl;
cout << a << endl;
MyClass* b= g();
cout << "g() exited" << endl;
cout << *b << endl;
}
输出为:
main() entered
f() entered
MyClass constructor
MyClass destructor
f() exited
MyClass stringifier
g() entered
MyClass constructor
MyClass destructor
g() exited
MyClass stringifier
现在,当我将鼠标悬停在语句上时,CLion 确实给了我一些警告:return
有趣的是,这篇现在相当古老的文章提到函数生成的场景是“无效的”C++。然而,我的编译器似乎不同意。f()
令我惊讶的是,这个程序编译和运行没有问题。我不应该在尝试打印和运行时收到“悬空引用”错误吗?我不认为这属于返回值优化/复制省略,因为我没有返回任何地方的新实例:复制构造函数显然没有在任何地方调用,因为我们看不到它的打印副作用。a
*b
MyClass
编辑:2011 年发布的这篇 SO 帖子的公认答案似乎也表明这里所做的不应该起作用。f
答:
下面是一个简单的示例,使用您提供的相同 MyClass:
int main() {
MyClass* ptr = g();
std::ostream << *ptr << std::endl;
}
从技术上讲,取消引用这样的错误指针是未定义的行为。但正如你所指出的,它只是碰巧起作用。就此而言,甚至可以返回 NULL,并且该程序仍然可能有效。但这就是为什么它恰好起作用的原因:g()
归根结底,您定义的那些 C++ 方法在逻辑上被编译为函数,就像 C 函数一样 - 除了考虑了“this”指针和重载的名称修改。因此,编译器(无名称修改)生成了一个如下函数:
std::ostream* MyClass_operator_ostream(std::ostream* os, MyClass* this) {
os->write("MyClass stringifier", 19);
}
所以你的主要工作基本上是这样做的:
MyClass* ptr = g();
MyClass_operator_ostream(&cout, ptr);
但是,流运算符实现实际上并不使用“this”指针。因此,实际上没有生成会触及该错误指针的代码。因此,没有悬而未决的引用实际上被击中。
现在,假设您扩展了 MyClass 以包含成员变量,然后您的流运算符重载引用了该变量。
class MyClass {
int value; // gets assigned by constructor
...
friend std::ostream& operator<< (std::ostream& os, const MyClass &myClass){
return os << "MyClass stringifier. value = " << value;
}
...
}
因此,编译器将再次生成如下代码:
std::ostream* MyClass_operator_ostream(std::ostream* os, MyClass* this) {
os->write("MyClass stringifier. value = ", 30);
const char* tmp = some_internal_code_to_convert_int_to_string(this->x);
os->write(tmp, strlen(tmp));
}
哎呀,现在已经习惯了。如果为 null,它肯定会崩溃。如果指向来自先前调用的函数的堆栈内存,它可能会打印出预期值。此时,它更有可能打印出堆栈上的任何内存。如果在 和 之间调用了另一个函数,则尤其如此。this->x
this
this
g()
cout << *g() << endl
我上面所说的一切都与返回指针的函数有关。但功能是一样的。编译器将引用视为后台的指针。g()
f()
但从技术上讲,这是未定义的行为,你不能依赖我在整个答案中所说的任何事情来成立。但这是最可能的解释。
评论
<source>:30:12: warning: reference to local variable 'x' returned [-Wreturn-local-addr]
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
CMakeLists.txt