在 C++ 中,如何修复指针 class 的变量在我调用它时变为 nullptr?

In C++, how do I fix a pointer class's variable becoming a nullptr when I call it?

我想在 class: class1 中使用 class: class2。根据我的阅读,为了防止循环依赖,必须在 class1.h 中转发声明 class2 并将其作为指针。在我的 class1.cpp 文件中从 class2 调用函数后。我无法调用 class2 中的变量而不会出现“无法读取内存”或 nullptr。

这是我的代码,感谢您的帮助:

//main.cpp

#include "Login.h"

#include <iostream>

using namespace std;

int main() {
    Login login;

    login.StartMenu();
    cout << "ENDING" << endl;

    system("pause");
    return 0;
}

//Login.h (Class1)

#pragma once

#include <iostream>

using namespace std;

class GameManager;

class Login {
public:
    void StartMenu();
    

private:
    GameManager* manager;

};

//Login.cpp

#include "Login.h"
#include "GameManager.h"

void Login::StartMenu() {
    manager->GameStart();

}

//GameManager.h (Class2)

#pragma once

class GameManager {
public:
    void GameStart();
    
private:
    int level = 1;

};

//GameManager.cpp

#include "Login.h"
#include "GameManager.h"

void GameManager::GameStart() {
    cout << level;

}

通常,将 headers 之间的依赖关系保持在最低限度是个好主意,并且使用仅 forward-declared 的 class 指针是一种既定的方法.即使没有循环依赖,这也是一种很好的做法,因为它可以大大减少大型项目中的重新编译时间。

关于您的具体问题:本质上,Login class,尤其是 Login::StartMenu 函数,需要知道要使用哪个 GameManager 实例。指向该实例的指针将存储在 manager 中。理想情况下,您可以通过 GameManager * 构造函数参数在 Login 实例的构造时告诉它:

#ifndef LOGIN_H
#define LOGIN_H

class GameManager;

/// This class handles the login procedure for a specific
/// game manager which must be provided to the constructor.
/// It cannot be copied (so it cannot be 
/// in arrays) or default-constructed. 
class Login {
public:
    /// The constructor does nothing except initializing manager.
    /// @param gmPtr is a pointer to the game manager
    /// this instance is using.
    void Login(GameManager *gmPtr)
               : manager(gmPtr) { /* empty */ }
    void StartMenu();
private:
    GameManager* manager;
};
#endif // LOGIN_H

为了完整起见,以下是您将如何使用它:

#include "Login.h"
#include "GameManager.h"

#include <iostream>
using namespace std;

int main() {
    GameManager gm;
    Login login(&gm); // <-- provide game manager to login

    login.StartMenu();
    cout << "ENDING" << endl;

    system("pause");
    return 0;
}

如果这是不可能的,因为 GameManager 实例尚不存在,或者在构建 Login 实例期间未知(例如,如果您有一个 Login 实例,其元素必须是 default-constructed) 您可以向 Login::StartMenu 方法提供参数。但是构造函数参数更受欢迎,因为您可以确保 class 在代码的其余部分中起作用——这种“不变量”是构造函数存在的主要原因。

如果所有函数都获得该指针参数,您当然有可能根本不需要保存指针。 Login class 是否与 GameManager 有 one-to-one 关系(在这种情况下它只是持有指向它的指针)或没有(在这种情况下每个函数都被告知每个时间)是一个设计决定。