给定此代码:
#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上,如果它使一个差异。
2条答案
按热度按时间zd287kbt1#
这三段代码都是不正确的,
label_
只是一个指向临时std::string
对象"Hello World"
的指针,作为一个临时对象,你不能保证字符串在x->print()
的时候仍然在label_
所指向的位置。如果我们使用优化,编译器将发出悬挂引用警告,奇怪的是,只有这样它才意识到问题。
在gcc 13.2中使用编译器标志
-Wall -Wextra -O3
:https://godbolt.org/z/9xjsxhrTT
推测一下,也许是因为临时变量在
main
中,在这里声明了对象,因此在作用域中,尽管是一个参数,但允许它存活足够长的时间。在第三种情况下,临时变量被直接传递给基构造函数,因此它可能会在x->print()
之前被丢弃。main
(动作发生的位置)不知道临时的。来自Java或C#,除了原始类型之外的所有内容都通过引用传递,没有任何顾虑,这可能会引起一些混乱,事实是C++不是这样,程序员有责任选择,引用类成员不会保存外部引用的数据,如果它是临时的,它会在程序认为适合其内存管理时立即消失。在这种情况下,如注解部分所述,您应该通过值传递数据,而不是通过引用,
label_
的所有者是Foo
,它应该存储在那里。xhv8bpkk2#
在“正常”情况下,将
const
引用绑定到临时对象会将临时对象的生存期延长到引用的生存期。例如,考虑这样的代码:字符串
foo
返回的string
是一个临时对象,其生存期通常在创建它的完整表达式(return
语句)结束时到期。但是,因为我们将它绑定到
const
引用,它的生存期被扩展到引用的生存期,所以当bar
打印它时,行为被完全定义。但是,当涉及的引用是类的成员时,这并不适用。标准没有直接解释为什么会这样,但我怀疑这主要是一个容易或难以实现的问题。
如果我有像
Foo const &foo = bar();
这样的东西,编译器必须“知道”bar()
的声明,以及它的返回类型。它还直接“知道”foo
是对const
的引用,因此返回的内容和生命周期扩展之间的联系相当直接和直接。然而,当你在类的内部存储一些东西时,编译器(至少可能)无法访问该类的内部。例如,在第三种情况下,编译器可以编译
main
,因为只看到了关于Base
和Derived
的这些内容:型
基于此,编译器无法知道传递给ctor的字符串与
label_
有任何关系,或者print()
使用label_
。只有通过分析类内容中的数据流(在编译调用代码时可能不可用),它才能弄清楚
label_
存储了什么或如何使用它。当代码可能不可用时,要求编译器分析代码将导致语言无法实现。即使所有的源代码都是可用的,这种关系也可能是任意复杂的,在某些时候,编译器将不再能够确定发生了什么,并找出它需要做什么。