前置声明在代码重构后会导致错误

5

我的原始类结构类似于:

//def.h
namespace A
{
   struct X {};
}

需要在必要的地方添加前向声明:

//file that needs forward declarations
namespace A { struct X; }

在进行了一些重构后,X被移动到了另一个命名空间中,但为了使旧代码“工作”,使用了using指令:

//def.h
namespace B
{
   struct X {};
}
namespace A
{
   using ::B::X;
}

现在我们可以继续使用旧的语法A::X来访问相同的类,但是前向声明会导致错误。第二个问题是我收到的错误信息没有指出前向声明所在的位置,查找和替换前向声明非常耗时。
目前,我通过一种(困难)的方式解决了这个问题。
如何处理这种情况最好的方法?
在我看来,using根本不应该存在,并且所有使用X的代码都应该重构以适应新的命名空间(这是一种解决方案),但不幸的是这并不可行。
实际代码要复杂得多,这只是一个简化的例子。

@K-ballo 这只是一个名称,前向声明是针对每个实现文件的。 - Luchian Grigore
X是被移动到已存在的命名空间B中,还是命名空间A被重命名为B?如果是后者,您是否可以使用命名空间别名? - TemplateRex
@rhalbersma 现有的命名空间 - A 仍然存在。 - Luchian Grigore
@LuchianGrigore 你的错误信息是什么? - TemplateRex
@LuchianGrigore,你正在混合使用“直接”的前向声明和通过using声明的“间接”的前向声明。将所有前向声明转换为后一种形式,并放在单独的fwd头文件中应该会有所帮助。 - TemplateRex
显示剩余4条评论
2个回答

4

我知道这更多是关于新代码而不是重构现有代码,但我喜欢在这种情况下使用一个特殊的头文件叫做X_fwd.hpp

// X_def.hpp
namespace B
{
   struct X {};
}
namespace A
{
   // NOT: using namespace B; // does not participate in ADL!      
   typedef ::B::X X;  // OR: using ::B::X;
}

// X_fwd.hpp
namespace A { struct X; }

// some file needing declaration of X
#include <X_fwd.hpp>

这使得查找前向声明变得更加容易,也更容易在事后进行更改,因为更改仅在一个地方隔离(DRY...)。
注意1:据我所知,在使用Peter Wood的typedef和您的using声明之间没有技术区别。请注意,using指令using namespace B;可能会引起问题,因为这些被参数相关查找忽略。更糟糕的是,您的一些代码可能会静默调用错误的函数重载,因为您不再引入新的命名空间B
注意2:在问题的评论中,提供了一个Ideone示例。这很好地说明了命名空间内部名称查找的微妙之处:引用草案Standard,第3.4.3.2节 命名空间成员[namespace.qual],第2款。
对于命名空间 X 和名称 m,命名空间限定查找集合 S(X, m) 定义如下:令 S'(X, m) 为 X 中所有声明 m 的声明和 X 的内联命名空间集合 (7.3.1)。如果 S'(X, m) 不为空,则 S(X, m) 为 S'(X, m);否则,S(X, m) 是 X 中的 using-directives 提名的所有命名空间 Ni 和其内联命名空间集合的 S(Ni, m) 的并集。
这解释了以下棘手的歧义。
namespace A
{
    struct X1{};
    struct X2{};
}

namespace B
{
    using A::X1;    // OK: lookup-set is both namespace A and B, and a single unique name is found (at this point!)
    struct X1;      // OK: lookup-set is namespace B, and a single unique name is found

    struct X2;      // OK: lookup-set is namespace B, and a single unique name is found
    using A::X2;    // error: lookup-set is both namespace A and B, and no unique name is found (at this point!)
}

因此,在命名空间中同时具有直接声明和使用声明相同名称的有效性是依赖于顺序的。因此,在前向头文件中只有一个声明是方便的。

在结构体声明和typedef共享名称仍然是一个错误。而且,typedef也不会影响ADL。 - Sebastian Redl
@SebastianRedl 谢谢,已更新以反映使用指令和typedef/using声明之间的微妙差别。 - TemplateRex
+1 是针对最后一个微妙之处的赞美 - 当我自己修复问题时,我看到了这一点,但无法解释原因。 - Luchian Grigore
@LuchianGrigore 我在查找精确细节时感到非常有趣。标准在第3.4节中开始非常滑稽:“名称查找规则适用于所有名称[...]”,然后跟着14页的未限定、限定、使用指令、使用声明和其他复杂细节;-) - TemplateRex

1

最好的方法是修复代码。

您可以分两步完成:

  1. 修复所有前向声明
  2. 删除 using ::B::X;

是的,我也这么想,这就是我所做的(不移除“using”指令,那不是一个选项)。 - Luchian Grigore

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