类型不完整,但我使用的是前向声明?

Incomplete type, but I'm using forward declarations?

本文关键字:声明 类型      更新时间:2023-10-16

我正在做的事情:我有两个文件:game.h和sound_controller.h。这两个文件都有冲突。game.h需要一个SoundController,而sound_controller.h需要一个game,所以它们都包含了对方的头文件。

问题:game.h第26行:

error: field soundController has incomplete type 'SoundController'

我已经在game.h中包含了sound_controller.h,但据说类型是不完整的,但我已经声明了类SoundController。那么我该如何解决这个问题呢?

代码:

game.h:

#pragma once
/**
game.h
Handles highest level game logic
*/
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include <SDL_mixer.h>
#include <iostream>
#include "config.h"
#include "sound_controller.h"
#include "image_controller.h"
#include <stdarg.h>
class SoundController; //forward declaration
class Game {
ImageController imageController;
SoundController soundController;
SDL_Window* window = NULL;
bool running;

public:
Game();
bool init();
bool createWindow();
void update();
static void log(const char* format, ...);
};

sound_controller.h:

#pragma once
/**
sound_controller.h
Allows for manipulation with sound (sound effects and music)
*/
#include "config.h"
#include <SDL_mixer.h>
#include "Game.h"
class Game; //forward declaration
class SoundController {
bool init();
bool load_sound();
bool load_music();
void music_play();
void music_pause();
void music_stop();
};

sound_controller.cpp使用Game是因为它调用Game.h的静态函数:log。

编辑:

从sound_controller.h中删除了"#include game.h"。现在这次在sound_concontroller.cpp中又出现了一个错误:

line 8 error: incomplete type 'Game' used in nested name specifier

sound_controller.cpp:

#include "sound_controller.h"
bool SoundController::init() {
bool success = true;
//Initialize SDL_mixer
if( Mix_OpenAudio( 44100, MIX_DEFAULT_FORMAT, 2, 2048 ) < 0 ) {
Game::log( "SDL_mixer could not initialize! SDL_mixer Error: %sn", Mix_GetError() );
success = false;
}
return success;
}

第2版:

解决方案是在sound_controller.cpp.中放入#include"game.h">

您有一个循环依赖项——每个头文件都试图#包含另一个;但不能两全其美:首先解析两个标头中的一个,当解析器解析第一个标头时,它(还)不知道第二个标头的内容(这就是它抱怨"不完整类型"的原因)。

在这种情况下,简单的解决方法是从sound_controller.h文件中删除"#include Game.h"行。

对我来说,你似乎仍然迷失了方向。让我们简化测试用例:

class A {
public:
A();
B b;
};
class B {
public:
B();
A a;
};
A::A() {;}
B::B() {;}

当编译器计算出A时,编译器必须知道其字段b的类型b。A的b是一个成员数据,其内存将在A实例化时分配。要知道这一点,编译器需要这样:

class B {
public:
B();
A a;
};

同样,为了让编译器弄清楚应该如何为B的a分配内存,编译器应该知道a。因此,这是一个循环依赖项,无法解决。

幸运的是,您的SoundController没有Game,而Game有SoundController。因此,不存在循环依赖关系。只有类SoundController{…}应该出现在类Game{…}之前。

然而,在我的例子中,如果A和B相互依赖,或者多个类之间存在循环依赖,这将是一个问题。

为了解决这个问题,如果字段的类型是类,则正向声明本身没有帮助。换句话说,如果在类本身被实例化时,应该为字段分配一些内存并进行布局,那么正向声明就没有帮助了。只有当字段的类型是指向其他类的指针或引用时,它才有帮助。在我的例子中,正向声明只有当A的字段b是b*或b&时才有效;。

class A {
public:
A();
B* b; // whatever the pointer points to, the size of the pointer is given
};
class B {
public:
B();
A a; // the compiler now can fine A from above
};

现在,编译器知道它将为A分配多少内存,以及内存布局是什么。在这种情况下,编译器不必知道B的大小,只需要知道指针的大小。当A的内存布局被解决时,编译器可以解决以下B的内存布局等问题。