【问题标题】:(C++) Linking with namespaces causes duplicate symbol error(C++) 与命名空间链接会导致重复符号错误
【发布时间】:2011-06-09 19:26:01
【问题描述】:

在过去的几天里,我一直在试图弄清楚如何为我一直在从事的 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

客户端还有游戏专用代码(在本例中为井字棋),分为声明和定义(TTT.h、Client.cpp)。 TTT.h 基本上是

#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

我决定把它放在一个命名空间中来分组它而不是一个类,因为有些部分在 OOP 中不能很好地工作,而且以​​后实现起来要容易得多。

我过去在链接客户端时遇到了问题,但此设置似乎有效。

我的服务器也分为两个文件,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 中),我得到一个重复的符号错误。由于我需要井字游戏功能,所以我在构建服务器时链接了 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

基本上,我正在寻找有关如何更好地组织我的代码以希望修复这些错误的任何建议。

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

-我尝试使用 static 和 extern 来解决这些问题,但显然这些不是我需要的

感谢所有花时间阅读所有内容并回复的人 =)

【问题讨论】:

  • 尝试使用标题保护,你没有使用任何。
  • 我在下面提供了一个答案,但也请像其他评论者指出的那样使用标题保护。
  • 由于他在 LINKING 时遇到问题,头球后卫可能不是他唯一的问题,尽管使用它们总是比不使用更好(我认为?)。
  • 在你的头文件中,你通常有像 void DoWork(); 这样的前向声明。如果你有一个函数体的实现,比如 void DoWork() { /* 你的代码在这里...*/ },并且这个头文件包含在一些 cpp 文件中,那么你的链接器有问题。为此,您可以实现一个 cpp 文件并将 DoWork 函数体排除在其中。我们在各个领域也遇到了类似的问题。要解决它,请移动 cpp 文件中的字段声明。当你在其他文件中需要它时,请参阅 maxelost 的解决方案

标签: c++ linker namespace-organisation


【解决方案1】:

您会收到有关重复定义的错误,因为这就是您所拥有的:每次 .cpp 文件包含 TTT.h 时,都会定义一个全局 bool PlayerIsX(在 nsTicTacToe 命名空间中,但仍然是全局的)。在这种情况下,包含它的是 Server.cpp 和 Client.cpp。

解决此问题的一种方法是使用 extern 将定义更改为声明,然后在相应的 .cpp 文件(例如 TTT.cpp)中进行实际定义。

在 TTT.h 中:

namespace nsTicTacToe {
   ...
   extern bool PlayerIsX;
   ...
}

在 TTT.cpp 中:

#include "TTT.h"

bool nsTicTacToe::PlayerIsX;

其他定义依此类推。

对了,记得好好保护#ifdefs:

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

【讨论】:

  • 非常感谢您的快速回复,并感谢您提供的所有有用信息。事实证明这正是我需要的。现在完美运行。
  • @Gfdking:没问题。不过,我也会推荐 lefticus 关于改进代码组织(使用结构或类)的建议。
【解决方案2】:

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

【讨论】:

    【解决方案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 中创建您的状态对象并将其传递给需要了解游戏系统状态的每个函数。

    从长远来看,这将导致更好的代码组织。

    【讨论】:

    • 谢谢。是的,我回想起来同意将数据保存在对象中会更好。虽然在这个阶段重组我的代码听起来不是很吸引人,但我一定会记住这一点 =)
    【解决方案4】:

    实际上,extern 正是您所需要的。您可能只是没有意识到或记得您还必须在 cpp 文件中定义此类变量。

    标题:

    extern int somevar;
    

    来源:

    int somevar = ?;
    

    通过将所有全局变量放在标头中,您将在包含它们的任何地方复制它们,这正是您的编译器所苦恼的。

    【讨论】:

    • 感谢您的回复!你是对的,事实证明我有点误解了如何实现extern。现已修复
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-09
    • 2013-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-10
    相关资源
    最近更新 更多