C++命名空间别名和前向声明

9

我正在使用一个C ++第三方库,它将所有的类都放置在版本化命名空间中,称之为tplib_v44。他们还定义了一个通用的命名空间别名:

namespace tplib = tplib_v44;

如果我在自己的.h文件中使用通用命名空间来前向声明库的成员...
namespace tplib { class SomeClassInTpLib; }

我在我的 .cpp 实现文件中后期包含第三方库时,遇到了头文件编译错误:

error C2386: 'tplib' : a symbol with this name already exists in the current scope

如果我使用版本特定的命名空间,那么一切都很好,但是...这有什么意义呢?最好的处理方法是什么?
[编辑] 未来的观众请注意,这是 ICU 库。在接受的答案评论中有一个解决方案(至少在我的情况下)。
5个回答

4

看起来有一个丑陋的解决方法,但没有好的解决方案。

对于ACE(附有合理的解释)Xerces(带有嘲讽的“这就是C++的工作方式”评论),它们定义了可以用于进行“通用化”处理的宏。

ACE_BEGIN_VERSIONED_NAMESPACE_DECL
class ACE_Reactor;
ACE_END_VERSIONED_NAMESPACE_DECL

XERCES_CPP_NAMESPACE_BEGIN
class DOMDocument;
class DOMElement;
XERCES_CPP_NAMESPACE_END

看起来像是一个不幸的C++产物,请在您的tplib中搜索这些宏。

标准将命名空间和命名空间别名视为不同的东西。您将tplib声明为命名空间,因此当编译器尝试稍后分配别名时,它无法同时成为两者,因此编译器会报错。


这非常接近最终适合我的解决方案。事实证明,我的库(ICU库)有一个小的头文件(uversion.h),其中定义了命名空间别名等内容。如果我在我的头文件中包含此头文件,则可以在我的头文件中使用他们定义的版本化命名空间宏(U_ICU_NAMESPACE),并在我的cpp文件中使用通用命名空间。因此,我可以免受版本化命名空间更改的影响,同时具有最小的编译时依赖性。 - Dave Mateer

2

我认为你的问题是由于tplib只是一个别名而不是真正的命名空间所致。

由于版本控制在第三方库中,你可能无法使用它,但是在未经版本控制的命名空间中使用带版本号的命名空间(而不是别名)似乎可以在g++ 4.0.1和4.1.2上工作。然而,我有一种感觉这不应该起作用...也许还有其他我不知道的问题。

//This is the versioned namespace
namespace tplib_v44
{
   int foo(){ return 1; }
}

//An unversioned namespace using the versioned one
namespace tplib
{
  using namespace tplib_v44;
}


//Since unversioned is a real namespace, not an alias you can add to it normallly.
namespace tplib
{
   class Something {};
}


int main()
{
  //Just to make sure it all works as expected
  tplib::foo();
}

请注意,如果在版本化的命名空间中已经声明了“Something”,则这不会引发错误。它将默默地接受,并且“tplib::Something”将引用版本化的命名空间,忽略“tplib::Something”。此外,对于使用“Something”作为参数的调用,ADL将不起作用 - 版本化的命名空间不会被查找候选项。 - Johannes Schaub - litb
@Johannes - 听起来很有道理。你能举个例子说明在使用情况下ADL会失败而直接命名空间不会吗? - Michael Anderson

0
“有什么意义呢?”
也许是为了防止您在他们的命名空间中添加其他内容,可能是因为他们认为很快会添加更多名称(使用版本化命名空间的事实可能表明这一点)。但这只是猜测。这样做的副作用是防止在您认为更合理的命名空间中进行前向声明,因此我认为这只是他们方面的不良编程实践。
“最好的处理方法是什么?”
没有最好的方法,但尽量避免使用宏,宏很丑陋,看起来不好(我不喜欢所有那些大写字母)。如果您担心“当他们更改版本时会发生什么?”(是的,在理论上,您必须在所有代码中更改“v44”为“v45”)
那么只需使用一个头文件来前向声明您需要的所有内容即可。

TpLibForwards.hpp

#ifdef XXXXXX_TPLIB
   #error "XXXXXX_TPLIB is already taken, change to something else"
#endif
#define XXXXXX_TPLIB  tplib_v44 
//... and that's why I don't like keeping macros around..

namespace XXXXXX_TPLIB
{

    // FORWARD DECLARATIONS
    class A1;
    class A2;
    //...
}
namespace tplib = XXXXXX_TPLIB;
#undef XXXXXX_TPLIB

如果他们更改了库,那么您只需要应用1个更改且在1个文件中。许多程序员已经将前向声明放在一个点上,因为那样更易于管理,而且如果您不得不前向声明很多东西,您可以使其他头文件更加干净和易读

#include <TpLibForwards.hpp> // my forwards declarations

0

嗯...你说的对我来说似乎是相反的。相反,试图将您的类声明为 tplib 命名空间的成员有什么意义呢?(暂时忘记它甚至不是一个命名空间,而是一个命名空间别名,这就是为什么会出现错误的原因。)

很明显,您构建了一些基于命名空间和命名空间别名的版本控制系统。如果您的类首次在某个特定的“版本”命名空间中引入(例如44),那么它必须在该命名空间中声明。为什么要试图将您的类声明“回到过去”,即在命名空间的所有以前版本(例如43和30)中?您的类在以前的版本中不存在,因此您不应该强制将其放在那里。


1
我想我的原始问题没有表达清楚。命名空间别名是由第三方开发人员提供的,这样我就可以将他们的类称为 tplib::SomeClass。他们在内部使用版本化的命名空间。因此,在他们的下一个版本中,内部命名空间可能是 tplib_v50。但是,我不需要在所有文件中更改 tplib_v44::tplib_v50::,因为我正在使用别名。我没有在命名空间中声明我的类;只是在我的类的头文件中前向声明了该库中的一个类。希望这样说得清楚。 - Dave Mateer

0

编辑:有人指出我错过了问题的重点,请随意忽略!

除了其他答案中提到的问题之外,让我有点担心的是你试图首先将自己的代码添加到第三方定义的命名空间中。

命名空间存在的目的是为了防止冲突的符号(类、类型定义、枚举等)发生冲突,通过将它们放置在自己的命名空间中,从潜在的相同部分限定符中开发出一个独特的完全限定符号。如果将自己的代码添加到第三方的命名空间中,可能会导致问题,例如,如果他们决定在以后的版本中也要使用相同的符号(例如添加他们自己的SomeClassInTpLib),那么命名冲突的命名空间就会出现问题。这就是为什么向std命名空间添加内容通常是不好的做法。

一个更安全的解决方案是完全避免这个问题,只需使用自己的命名空间。将其称为tplib_ex或类似的名称,关联仍然清晰明了,但冲突不会成为问题,您的别名相关问题也将消失。


他并没有向库中添加符号,而是试图在第三方命名空间(别名)内前向声明已经存在的对象。 - Stephen

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