【问题标题】:In C++, how do I fix a pointer class's variable becoming a nullptr when I call it?在 C++ 中,如何在调用指针类的变量时将其修复为 nullptr?
【发布时间】:2022-01-01 16:42:49
【问题描述】:

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

这是我的代码,谢谢你的帮助:

//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;

};

//登录.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;

}

【问题讨论】:

  • 你没有在Login中初始化manager
  • 你在哪里初始化manager
  • to prevent a circular dependency 你说的是哪个循环依赖? GameManager 不使用 Login
  • 另外,你在各个地方都有奇怪的包含“GameManager.h”和“Login.h”,只包含正在使用它的标题。
  • 另外,你的设计很奇怪。理想情况下,GameManager 内部应该有一个 Login。

标签: c++ pointers initialization forward-declaration


【解决方案1】:

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

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

#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 实例数组,其元素必须为默认值-constructed)您可以将参数提供给Login::StartMenu 方法。但是构造函数参数更受欢迎,因为这样您就可以确定该类在其余代码中是正常的——这种“不变量”是构造函数存在的主要原因。

如果所有函数都获得该指针参数,那么您当然可能根本不需要持有指针。 Login 类是否与 GameManager 具有一对一的关系(在这种情况下,它只是保存一个指向它的指针)与否(在这种情况下,每次都会告诉每个函数)是一个设计决定。

【讨论】:

  • 构造函数应该是什么样子才能让 Login 类知道要使用哪个 GameManager 以及该构造函数是位于 Login.h 的私有部分还是 GameManager.h 的公共部分?
  • @IStrayDogI 我包含了一个带有构造函数内联实现的标头。
  • 有效!我能够在另一个类中实现我的其他类。非常感谢!
  • @IStrayDogI 很高兴。祝你好运。
  • 警告:现在您必须担心所有权和对象生存期:如果在销毁后使用其地址保存在 Login 类中的 GameManager,您的程序会崩溃。您可以使用智能指针来避免这种情况,但首先这是可以的。
猜你喜欢
  • 2022-01-11
  • 2020-08-04
  • 2016-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-30
  • 2018-01-23
  • 1970-01-01
相关资源
最近更新 更多