给出这段代码:
#include <iostream>
class Foo {
public:
Foo(const std::string& label) : label_(label) {}
void print() {
std::cout << label_;
}
private:
const std::string& label_;
};
int main() {
auto x = new Foo("Hello World");
x->print();
}
我明白了
Hello World!
当我运行它时。如果我这样修改它:
// g++ -o test test.cpp -std=c++17
#include <iostream>
class Base {
public:
Base(const std::string& label) : label_(label) {}
void print() {
std::cout << label_;
}
private:
const std::string& label_;
};
class Derived : public Base {
public:
Derived(const std::string& label) : Base(label) {}
};
int main() {
auto x = new Derived("Hello World");
x->print();
}
我仍然明白:
Hello World
但是如果我这样修改它:
// g++ -o test test.cpp -std=c++17
#include <iostream>
class Base {
public:
Base(const std::string& label) : label_(label) {}
void print() {
std::cout << label_;
}
private:
const std::string& label_;
};
class Derived : public Base {
public:
Derived() : Base("Hello World") {}
};
int main() {
auto x = new Derived();
x->print();
}
我没有得到任何输出。谁能向我解释一下吗?我正在这样编译程序:
g++ -o test test.cpp -std=c++17
如果有区别的话,这是在 Mac 上。
这三段代码都是不正确的,
label_
只是一个指向临时std::string
对象"Hello World"
的指针,作为一个临时的你不能保证字符串仍然位于label_
指向的位置。 x->print()
的时间。
如果我们使用优化,编译器将发出悬挂引用警告,奇怪的是只有这样它才会意识到问题。
在 gcc 13.2 中使用编译器标志
-Wall -Wextra -O3
:
https://godbolt.org/z/9xjsxhrTT
推测,也许临时对象位于声明对象的地方,因此在范围内,尽管是一个参数,但允许它存在足够长的时间。在第三种情况下,临时值直接传递给基本构造函数,因此它可能会在
main
之前被丢弃。 x->print()
,动作发生的地方,不知道临时的情况。来自 Java 或 C#,除了基本类型之外的所有内容都通过引用传递,无需担心,这可能会引起一些混乱,事实是 C++ 情况并非如此,程序员有责任选择引用类成员不会保存外部引用的数据,如果它是临时的,一旦程序认为适合其内存管理,它就会消失。在这种情况下,如评论部分所述,您应该按值传递数据,而不是按引用传递数据,main
的所有者是
label_
,它是应该存储的位置。Foo
引用绑定到临时对象会将临时对象的生命周期延长到引用的生命周期。例如,考虑这样的代码:
const
std::string foo() { return "Hello World"; }
void bar() {
std::string const& extended_life = foo();
std::cout << extended_life << "\n";
}
返回的
string
是一个临时对象,其生命周期通常会在创建它的完整表达式(foo
语句)结束时到期。但是,因为我们将它绑定到 return
引用,所以它的生命周期被延长到引用的生命周期,所以当
const
打印它时,行为就被完全定义了。当涉及的引用是类的成员时,这并不适用。该标准没有直接解释为什么会出现这种情况,但我怀疑这主要是一个容易或难以实施的问题。
如果我有类似
bar
的东西,编译器必须“知道”
Foo const &foo = bar();
的声明,并从中了解它的返回类型。它还直接“知道”bar()
是对foo
的引用,因此返回的内容和生命周期延长之间的联系相当直接和直接。但是,当您在类内部存储某些内容时,编译器(至少可能)无法访问该类的内部结构。例如,在第三种情况下,编译器可以在仅了解 const
和
main
的情况下编译 Base
:Derived
基于此,编译器无法知道传递给 ctor 的字符串是否与
class Base {
public:
Base(const std::string& label);
void print();
private:
const std::string& label_;
};
class Derived : public Base {
public:
Derived();
};
相关或
label_
使用 print()
。只有通过分析类内容的数据流(在编译调用代码时可能不可用),它才能弄清楚 label_
存储什么或如何使用它。当代码可能不可用时要求编译器分析该代码将导致无法实现语言。即使所有源代码都可用,这种关系也可能任意复杂,并且在某些时候,编译器将不再能够确定发生了什么并弄清楚它需要做什么。