c++ std::string引用类成员的奇怪行为

chy5wohz  于 2023-08-09  发布在  其他
关注(0)|答案(2)|浏览(103)

给定此代码:

#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上,如果它使一个差异。

zd287kbt

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,它应该存储在那里。

xhv8bpkk

xhv8bpkk2#

在“正常”情况下,将const引用绑定到临时对象会将临时对象的生存期延长到引用的生存期。例如,考虑这样的代码:

std::string foo() { return "Hello World"; }

void bar() {
    std::string const& extended_life = foo();
    std::cout << extended_life << "\n";
}

字符串
foo返回的string是一个临时对象,其生存期通常在创建它的完整表达式(return语句)结束时到期。
但是,因为我们将它绑定到const引用,它的生存期被扩展到引用的生存期,所以当bar打印它时,行为被完全定义。
但是,当涉及的引用是类的成员时,这并不适用。标准没有直接解释为什么会这样,但我怀疑这主要是一个容易或难以实现的问题。
如果我有像Foo const &foo = bar();这样的东西,编译器必须“知道”bar()的声明,以及它的返回类型。它还直接“知道”foo是对const的引用,因此返回的内容和生命周期扩展之间的联系相当直接和直接。
然而,当你在类的内部存储一些东西时,编译器(至少可能)无法访问该类的内部。例如,在第三种情况下,编译器可以编译main,因为只看到了关于BaseDerived的这些内容:

class Base {
    public:
        Base(const std::string& label);
        void print();
    private:
        const std::string& label_;
};

class Derived : public Base {
    public:
        Derived();
};


基于此,编译器无法知道传递给ctor的字符串与label_有任何关系,或者print()使用label_
只有通过分析类内容中的数据流(在编译调用代码时可能不可用),它才能弄清楚label_存储了什么或如何使用它。当代码可能不可用时,要求编译器分析代码将导致语言无法实现。即使所有的源代码都是可用的,这种关系也可能是任意复杂的,在某些时候,编译器将不再能够确定发生了什么,并找出它需要做什么。

相关问题