I want to use a class: class2, within a class: class1. From what I read, to prevent a circular dependency, one must forward declare class2 in class1.h and have it be a pointer. After calling a function from class2 in my class1.cpp file. I'm unable to call the variables within class2 without getting "Unable to read memory" or a nullptr.
Here's my code, thank you for the help:
//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;
}
CodePudding user response:
Generally, it is a good idea to keep dependencies between headers to a minimum, and using pointers for classes that are only forward-declared is an established way to do that. This is good practice even if there are no circular dependencies because it can greatly reduce recompilation times in large projects.
Regarding your specific question: Essentially, the Login
class, and especially the Login::StartMenu
function, needs to know which GameManager
instance to use. A pointer to that instance will be stored in manager
. Ideally you can tell that at construction time of a Login
instance via a GameManager *
constructor argument:
#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
For completeness, here is how you would use it:
#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;
}
If that is not possible because the GameManager
instance does not exist yet or is otherwise unknown during construction of a Login
instance (for example, if you have an array of Login
instances, whose elements must be default-constructed) you can provide the argument to the Login::StartMenu
method. But the constructor argument is much preferred because you can then be sure that the class is functional in the rest of the code — this kind of "invariants" are the main reason why constructors exist.
It is certainly possible that you don't need to hold a pointer at all, if all functions get that pointer argument. Whether the Login
class has a one-to-one relationship with a GameManager
(in which case it simply holds a pointer to it) or not (in which case every function is told each time) is a design decision.