对基类的引用的 C++ 未定义行为

c++ undefined behavior of a reference to a base class

提问人:Miroslav Krajcir 提问时间:11/14/2023 更新时间:11/14/2023 访问量:107

问:

请看我的代码。我有一个基类“System”和一个派生类“AdvancedSystem”。访问基类引用时,输出是未定义且随机的,我不知道为什么。访问派生类为我提供了预期的输出。有什么想法吗?

#include <iostream>
#include <vector>

class System {
public:
    System() :
        someValue(12345)
    {}
 
    int someValue;
};

class AdvancedSystem : public System {
public:
    AdvancedSystem()
    {}
};

class Strategies {
public:
    Strategies(System& system) :
        system(system)
    {}

    System& system;
};

class Test {
public:
    Test() :
        system(),
        strategies(system)
    {}

    AdvancedSystem system;
    Strategies strategies;
};

class Tests {
public:
    void createTests() {
        for (int i = 0; i < 18; ++i) {
            values.emplace_back();
        }
    }

    std::vector<Test> values;
};

int main() {
    Tests tests;
    tests.createTests();
    std::cout << tests.values[0].system.someValue << "\n"; // outputs expected value of 12345
    std::cout << tests.values[0].strategies.system.someValue << "\n"; // outputs random number
}
C++ 继承向 引用 回推

评论

4赞 molbdnilo 11/14/2023
当增长并被重新分配时,您的所有引用都将失效。values
0赞 Miroslav Krajcir 11/14/2023
很酷,谢谢。这里的解决方案是什么?共享指针?
1赞 molbdnilo 11/14/2023
这是为了共享对象,所以它可能很好。(引用成员几乎总是一个错误。
0赞 463035818_is_not_an_ai 11/14/2023
std::list大多数时候性能较差,但有时这并不重要,它有助于稳定的迭代器(列表不会重新分配)
1赞 n. m. could be an AI 11/14/2023
也许根本不应该参考。由于对两者的所有访问都是通过 的,后者知道相关对象在哪里,并将其传递给方法。StrategiesSystemTestSystemStrategies

答:

3赞 Red.Wave 11/14/2023 #1

使用引用作为类成员通常是设计不良的标志。在你的情况下,情况更糟,因为你创建了自我防护的对象。在这样的 senarios 中,你不能依赖规则 0,而应该依赖规则 3规则 5

在向量上的每一个向量上,向量都会被重新分配,并且值会从旧分配移动到新分配。由于遵循规则 0,因此复制/移动由自动生成的代码处理;因此,引用是按位复制的,并且由于原始的自引用对象被破坏,因此最终会得到一个悬空引用,引用无处(随机内存位置)。这是UB的通用案例。您可以通过预先分配向量 via 方法来推迟灾难,但这只会隐藏实际问题并为灾难争取时间。emplace_backdefaultsystemvector::reserve

最终的解决方案是修改您的设计,以防止在任何情况下出现悬空参考问题。您需要为您的类正确定义复制或移动构造函数。这就是为什么我建议您遵循第 1 段中的建议。