为什么我不能在未命名的命名空间中声明变量后在全局范围内定义它?
Why can't I define a variable at global scope after declaring it in an unnamed namespace?
据我了解:
- 在未命名的命名空间中 声明的 名称是 added to the enclosing namespace scope and visible in that translation unit。
- 使用
extern
说明符,变量不会自动 定义 (默认初始化)。
我希望下面的代码声明一个变量,然后定义同一个变量:
// Declare ::<unnamed>::my_obj with internal linkage and add my_obj to the
// enclosing namespace scope (the global namespace scope).
namespace { extern int my_obj; }
// Expected: Define the variable declared above.
// Actual: Declare and define a new object, ::my_obj.
int my_obj(42);
相反,它声明了两个不同的对象,并警告我未使用的 extern int my_obj
。
为什么第二个 my_obj
没有定义第一个 my_obj
?不在范围内吗?
未命名的命名空间不是全局命名空间。它是一个特定的命名空间,仅在它出现的翻译单元内可见。
This cppreference page 对此进行了更详细的描述。
如果您试图在同一个翻译单元中使用未命名命名空间中定义的符号——就使用它吧!它在范围内。你不需要 extern
来自 https://msdn.microsoft.com/en-us/library/2tx32sw2.aspx :
使用 extern storage-class 说明符声明的变量是对在程序的任何源文件的外部级别定义的同名变量的引用。内部 extern 声明用于使外部级变量定义在块内可见。 除非在外部级别另外声明,否则使用 extern 关键字声明的变量仅在声明它的块中可见。
由于对象是在命名空间中声明的,所以它必须在同一个命名空间中定义(或者更确切地说,如果一个名称用作一个命名空间中的声明和该命名空间之外的定义,它指的是两个不同的实体).它在文件范围内 visible 并不意味着它是该名称空间的成员;我认为断言 —
Names declared in an unnamed namespace are added to the enclosing
namespace scope
— 并不代表你认为的那样。
C++11 标准说 未命名命名空间定义的行为就好像它被替换为:
[inline] namespace unique { /* empty body */ }
using namespace unique ;
namespace unique { namespace-body }
(为了格式化而稍微解释一下;初始的 "inline" 是可选的,如果它出现在未命名的命名空间定义中)。
所以,你会期待这个:
namespace a_name {
extern int a;
}
using namespace a_name;
int a = 4;
... 第一次提到 a
(在命名空间内)会声明变量,第二次会定义它? (如果你愿意,至少这是一致的。但如果你不愿意,你需要认识到标准明确指出你应该从未命名的命名空间获得相同的行为)。
我不相信有任何机制可以在一个命名空间中声明一个成员并在另一个命名空间中为同一成员提供定义,即使该成员在第二个命名空间中可见。
解决以下评论:
- 是的,
extern
导致声明不是定义。我去掉了extern
没有作用的说法,主要是参考linkage; extern
不在未命名命名空间外部授予名称 linkage 是正确的,因为 C++11(包括修正)。尽管在 C++03 中它可能在技术上授予了外部 linkage,但这只是作为 declaration/definition 的一个属性;该符号在翻译单元外仍然不可见,并且对 linker 没有特殊可见性。 extern
对 C++03 中未命名命名空间成员的重要性显然归结为该成员作为常量表达式的可用性,如果它是 const
限定的指针。
- 评论中的 example that was linked 紧接着另一条评论指出 link 年龄决定了符号对 link 人的可见性,这可能会产生后果,也许是为了证明(在 C++11 中)未命名命名空间中的符号可以有外部 linkage,并且非外部 linkage 指针不能用作模板参数(即需要常量表达式的上下文)甚至声明时
const
。它确实无法使用 GCC 进行编译,但是它可以使用 Clang 和 Intel 编译器 Icc 进行成功编译。对于这三个编译器中的任何一个,在内联命名空间中声明的符号都是 link 级别的局部符号(例如使用 objdump
可以看出) - 在符号可见性之间没有因果关系linker 级别和产生的错误。
C++11 明确声明 (3.5) 的效果是
extern
-合格符号应具有内部 linkage 如果它们
驻留在未命名的命名空间中(具体来说:A name having
上面没有给出 internal linkage 的命名空间范围与封闭的命名空间具有相同的 linkage 如果它是一个变量的名称;或 ...)。此外,const
声明的内部 linkage 变量应该可以在需要常量表达式的上下文中使用。通过阅读标准,几乎没有理由认为 GCC 编译器在这种情况下行为不正确。
声明"names declared in an unnamed namespace are added to the enclosing namespace scope"并不意味着未命名命名空间的成员成为封闭命名空间的完整成员。此添加由 隐式使用指令 执行。这样的添加使这些名称对名称查找可见,但不会使它们成为用于其他目的的封闭名称空间的文字成员。
您的代码中遇到的问题与以下代码段中的问题相同
namespace A
{
void foo();
}
using namespace A;
void foo() // <- Defines `::foo`, not `A::foo`
{
}
// `A::foo` remains undefined
尽管我们显式"added"将A
命名为全局命名空间,但这并不意味着我们可以定义A::foo
作为全局命名空间的成员使用非限定名称 foo
.
为了在上面的示例中定义 A::foo
,您仍然必须使用其限定名称 A::foo
来引用它。对于未命名的命名空间,这是不可能的,原因很明显。
P.S。编译器通常将未命名的命名空间实现为具有内部编译器生成的名称的命名空间。如果您以某种方式想出 "hack" 来发现该名称,从技术上讲,您可能能够通过在定义中使用限定名称来单独定义 my_obj
。但请记住,隐藏的命名空间名称在不同的翻译单元中是不同的,因此在每个翻译单元中都会产生一个唯一的 my_obj
变量。
据我了解:
- 在未命名的命名空间中 声明的 名称是 added to the enclosing namespace scope and visible in that translation unit。
- 使用
extern
说明符,变量不会自动 定义 (默认初始化)。
我希望下面的代码声明一个变量,然后定义同一个变量:
// Declare ::<unnamed>::my_obj with internal linkage and add my_obj to the
// enclosing namespace scope (the global namespace scope).
namespace { extern int my_obj; }
// Expected: Define the variable declared above.
// Actual: Declare and define a new object, ::my_obj.
int my_obj(42);
相反,它声明了两个不同的对象,并警告我未使用的 extern int my_obj
。
为什么第二个 my_obj
没有定义第一个 my_obj
?不在范围内吗?
未命名的命名空间不是全局命名空间。它是一个特定的命名空间,仅在它出现的翻译单元内可见。
This cppreference page 对此进行了更详细的描述。
如果您试图在同一个翻译单元中使用未命名命名空间中定义的符号——就使用它吧!它在范围内。你不需要 extern
来自 https://msdn.microsoft.com/en-us/library/2tx32sw2.aspx :
使用 extern storage-class 说明符声明的变量是对在程序的任何源文件的外部级别定义的同名变量的引用。内部 extern 声明用于使外部级变量定义在块内可见。 除非在外部级别另外声明,否则使用 extern 关键字声明的变量仅在声明它的块中可见。
由于对象是在命名空间中声明的,所以它必须在同一个命名空间中定义(或者更确切地说,如果一个名称用作一个命名空间中的声明和该命名空间之外的定义,它指的是两个不同的实体).它在文件范围内 visible 并不意味着它是该名称空间的成员;我认为断言 —
Names declared in an unnamed namespace are added to the enclosing namespace scope
— 并不代表你认为的那样。
C++11 标准说 未命名命名空间定义的行为就好像它被替换为:
[inline] namespace unique { /* empty body */ }
using namespace unique ;
namespace unique { namespace-body }
(为了格式化而稍微解释一下;初始的 "inline" 是可选的,如果它出现在未命名的命名空间定义中)。
所以,你会期待这个:
namespace a_name {
extern int a;
}
using namespace a_name;
int a = 4;
... 第一次提到 a
(在命名空间内)会声明变量,第二次会定义它? (如果你愿意,至少这是一致的。但如果你不愿意,你需要认识到标准明确指出你应该从未命名的命名空间获得相同的行为)。
我不相信有任何机制可以在一个命名空间中声明一个成员并在另一个命名空间中为同一成员提供定义,即使该成员在第二个命名空间中可见。
解决以下评论:
- 是的,
extern
导致声明不是定义。我去掉了extern
没有作用的说法,主要是参考linkage;extern
不在未命名命名空间外部授予名称 linkage 是正确的,因为 C++11(包括修正)。尽管在 C++03 中它可能在技术上授予了外部 linkage,但这只是作为 declaration/definition 的一个属性;该符号在翻译单元外仍然不可见,并且对 linker 没有特殊可见性。extern
对 C++03 中未命名命名空间成员的重要性显然归结为该成员作为常量表达式的可用性,如果它是const
限定的指针。 - 评论中的 example that was linked 紧接着另一条评论指出 link 年龄决定了符号对 link 人的可见性,这可能会产生后果,也许是为了证明(在 C++11 中)未命名命名空间中的符号可以有外部 linkage,并且非外部 linkage 指针不能用作模板参数(即需要常量表达式的上下文)甚至声明时
const
。它确实无法使用 GCC 进行编译,但是它可以使用 Clang 和 Intel 编译器 Icc 进行成功编译。对于这三个编译器中的任何一个,在内联命名空间中声明的符号都是 link 级别的局部符号(例如使用objdump
可以看出) - 在符号可见性之间没有因果关系linker 级别和产生的错误。
C++11 明确声明 (3.5) 的效果是extern
-合格符号应具有内部 linkage 如果它们 驻留在未命名的命名空间中(具体来说:A name having 上面没有给出 internal linkage 的命名空间范围与封闭的命名空间具有相同的 linkage 如果它是一个变量的名称;或 ...)。此外,const
声明的内部 linkage 变量应该可以在需要常量表达式的上下文中使用。通过阅读标准,几乎没有理由认为 GCC 编译器在这种情况下行为不正确。
声明"names declared in an unnamed namespace are added to the enclosing namespace scope"并不意味着未命名命名空间的成员成为封闭命名空间的完整成员。此添加由 隐式使用指令 执行。这样的添加使这些名称对名称查找可见,但不会使它们成为用于其他目的的封闭名称空间的文字成员。
您的代码中遇到的问题与以下代码段中的问题相同
namespace A
{
void foo();
}
using namespace A;
void foo() // <- Defines `::foo`, not `A::foo`
{
}
// `A::foo` remains undefined
尽管我们显式"added"将A
命名为全局命名空间,但这并不意味着我们可以定义A::foo
作为全局命名空间的成员使用非限定名称 foo
.
为了在上面的示例中定义 A::foo
,您仍然必须使用其限定名称 A::foo
来引用它。对于未命名的命名空间,这是不可能的,原因很明显。
P.S。编译器通常将未命名的命名空间实现为具有内部编译器生成的名称的命名空间。如果您以某种方式想出 "hack" 来发现该名称,从技术上讲,您可能能够通过在定义中使用限定名称来单独定义 my_obj
。但请记住,隐藏的命名空间名称在不同的翻译单元中是不同的,因此在每个翻译单元中都会产生一个唯一的 my_obj
变量。