(C++) 带命名空间链接导致重复符号错误

5

最近几天,我一直在努力弄清楚如何链接一个CLI游戏项目中的文件。该项目有两个部分:客户端和服务器代码。

客户端需要使用我创建的两个库。第一个是通用游戏板。这个库被分成了GameEngine.h和GameEngine.cpp两个文件。头文件大致如下:

namespace gfdGaming {

//  struct sqr_size {
//      Index x;
//      Index y;
//  };
    typedef struct { Index x, y; } sqr_size; 
    const sqr_size sPos = {1, 1};
    sqr_size sqr(Index x, Index y);
    sqr_size ePos;
    class board
    {
    // Prototypes / declarations for the class
    }
}

而且CPP文件只是提供了所有内容

#include "GameEngine.h"

type gfdGaming::board::functions

客户端还有游戏特定的代码(在本例中为TicTacToe),分为声明和定义(TTT.h,Client.cpp)。TTT.h基本上是一个头文件,包含了TicTacToe类的声明和一些常量。Client.cpp包含了TicTacToe类的定义和客户端主函数。
#include "GameEngine.h"

#define TTTtar "localhost"
#define TTTport 2886

using namespace gfdGaming;
void* turnHandler(void*);
namespace nsTicTacToe 
{
    GFDCON gfd;
    const char X = 'X';
    const char O = 'O';
    string MPhostname, mySID;
    board TTTboard;
    bool PlayerIsX = true, isMyTurn;
    char Player = X, Player2 = O;

    int recon(string* datHolder = NULL, bool force = false);
    void initMP(bool create = false, string hn = TTTtar);
    void init();
    bool isTie();
    int turnPlayer(Index loc, char lSym = Player);
    bool checkWin(char sym = Player);

    int mainloop();

    int mainloopMP();

}; // NS

我决定将这个放在一个命名空间中进行分组,而不是放在类中,因为有一些部分在面向对象编程中表现不佳,而且后期实现起来更加容易。

过去我曾经遇到过链接客户端的问题,但是这种设置似乎可以解决。

我的服务器也分成了两个文件,Server.h和Server.cpp。

Server.h中包含以下内容:

#include "../TicTacToe/TTT.h" // Server needs a full copy of TicTacToe code

class TTTserv;
struct TTTachievement_requirement {
    Index id;
    Index loc;
    bool inUse;
};
struct TTTachievement_t {
    Index id;
    bool achieved;
    bool AND, inSameGame;
    bool inUse;
    bool (*lHandler)(TTTserv*);
    char mustBeSym;
    int mustBePlayer;
    string name, description;
    TTTachievement_requirement steps[safearray(8*8)];

};

class achievement_core_t : public GfdOogleTech {
public: // May be shifted to private
    TTTachievement_t list[safearray(8*8)];
public:
    achievement_core_t();

    int insert(string name, string d, bool samegame, bool lAnd, int lSteps[8*8], int mbP=0, char mbS=0);
};


struct TTTplayer_t {
    Index id;
    bool inUse;
    string ip, sessionID;
    char sym;
    int desc;
    TTTachievement_t Ding[8*8];
};
struct TTTgame_t {
    TTTplayer_t Player[safearray(2)];
    TTTplayer_t Spectator;
    achievement_core_t achievement_core;
    Index cTurn, players;
    port_t roomLoc;
    bool inGame, Xused, Oused, newEvent;
};


class TTTserv : public gSserver {
    TTTgame_t Game;
    TTTplayer_t *cPlayer;

    port_t conPort;
public:
    achievement_core_t *achiev;
    thread threads[8];
    int parseit(string tDat, string tsIP);
    Index conCount;
    int parseit(string tDat, int tlUser, TTTplayer_t** retval);

private:
    int parseProto(string dat, string sIP);
    int parseProto(string dat, int lUser);

    int cycleTurn();
    void setup(port_t lPort = 0, bool complete = false);

public:
    int newEvent;
    TTTserv(port_t tlPort = TTTport, bool tcomplete = true);

    TTTplayer_t* userDC(Index id, Index force = false);
    int sendToPlayers(string dat, bool asMSG = false);
    int mainLoop(volatile bool *play);
};



// Other 
void* userHandler(void*);
void* handleUser(void*);

在 CPP 文件中,我包括 Server.h,并提供 main() 函数和之前声明的所有函数的内容。

现在来看待问题。我在链接服务器时遇到了问题。更具体地说,对于 nsTicTacToe 中的每个变量(可能也包括 gfdGaming 中的变量),我都会收到重复符号错误。由于我需要使用 TicTacToe 函数,在构建服务器时连接 Client.cpp(没有 main() 函数)。

ld: duplicate symbol nsTicTacToe::PlayerIsX       in Client.o and Server.o
collect2: ld returned 1 exit status
Command /Developer/usr/bin/g++-4.2 failed with exit code 1

It stops once a problem is encountered, but if PlayerIsX is removed / changed temporarily than another variable causes an error

本质上,我正在寻求如何更好地组织我的代码以解决这些错误的任何建议。

免责声明: -如果我提供了太多或太少的信息,我在此提前道歉,因为这是我第一次发布

-我尝试使用静态和外部来解决这些问题,但显然那不是我需要的

感谢任何花时间阅读并回复的人 =)


尝试使用头文件保护,你没有使用任何保护。 - DumbCoder
1
我在下面提供了一个答案,但是像其他评论者所指出的那样,请使用头文件保护。 - lefticus
由于他在链接方面遇到了麻烦,头文件保护可能不是他唯一的问题,尽管使用它们总比不使用好(我认为?)。 - stnr
在您的头文件中,通常会有像void DoWork();这样的前向声明。如果您有一个函数体的实现,例如void DoWork() { /* your code goes here...*/ },并且某些cpp文件包含了此头文件,则您的链接器会出现问题。为此,您可以实现一个cpp文件,并将DoWork函数体排除在外。我们也会遇到类似的字段问题。要解决它,请将字段声明移动到cpp文件中。当您需要在其他文件中使用它时,请参考maxelost的解决方案。 - peter70
4个回答

7
您会看到有有关重复定义的错误,因为这确实是存在的:每次一个 .cpp 文件包含 TTT.h 时,一个全局变量 bool PlayerIsX 就被定义了一次(在 nsTicTacToe 命名空间中,但仍然是全局的)。在这种情况下,Server.cpp 和 Client.cpp 正是在包含它。
一种解决方法是通过使用 extern 将定义改为声明,然后在相应的 .cpp 文件(例如 TTT.cpp)中进行实际的定义。
在 TTT.h 中:
namespace nsTicTacToe {
   ...
   extern bool PlayerIsX;
   ...
}

In TTT.cpp:

#include "TTT.h"

bool nsTicTacToe::PlayerIsX;

其他定义同理。

顺便提醒,记得要有适当的防护 #ifdef

#ifndef __TTT_H
#define __TTT_H
... header contents
#endif   // __TTT_H

非常感谢您的快速响应,也感谢您提供的所有有用信息。结果证明这正是我所需要的。现在完美运作。 - Gfdking
1
@Gfdking:没问题。不过,我也建议采纳lefticus的建议,改善代码组织(使用结构体或类)。 - maxelost

4
实际上,extern 是你需要的。你可能只是没有意识到或者记得你还需要在 cpp 文件中定义这样的变量。
头文件:
extern int somevar;

来源:

int somevar = ?;

将所有全局变量放在头文件中会使它们在每次包含时都被复制,这正是编译器反对的内容。

1
谢谢您的回复!您是正确的,事实证明我稍微误解了如何实现extern。现在已经修复了。 - Gfdking

3

你实际上在使用全局变量,在C++中这是不推荐的,但在C中有时是必要的。

你可以使用extern让它工作,但“更好”的答案是将全局变量封装在某种状态对象中。

struct State
{
  GFDCON gfd;
  const char X;
  const char O;
  string MPhostname, mySID;
  board TTTboard;
  bool PlayerIsX, isMyTurn;
  char Player, Player2;
};

在Main中创建您的状态对象,并将其传递给需要了解游戏系统状态的每个函数。这样做可以使代码组织更加清晰。

谢谢。是的,回想起来,将数据保存在一个对象中会更好一些。虽然在这个阶段重构我的代码听起来并不很吸引人,但我肯定会记住这个建议的 =) - Gfdking

1

你可以将命名空间nsTicTacToe放入自己的.cpp文件中,单独编译它,然后链接它。 你可能还需要一个头文件,只声明变量的外部引用,并将其包含在客户端和服务器的.cpp文件中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接