C++命名空间成员访问不同文件如何? “命名空间标准”是如何实现的?

Posted

技术标签:

【中文标题】C++命名空间成员访问不同文件如何? “命名空间标准”是如何实现的?【英文标题】:C++ Namespace member access in different files how to? how "namespace std" implemented? 【发布时间】:2011-05-24 10:45:14 【问题描述】:

我在 sample.h 中声明了以下命名空间

// namespace with identifier
namespace N1

    int b = 80;

sample1.cpp 使用上面的命名空间声明

#include <iostream>
#include "sample.h"

using namespace std;
using namespace N1;

int main(void)

    cout << "b (in main) = " << b << endl;
      foo(); //written in sample2.cpp
      return 0;

sample2.cpp 也使用在 sample.h 中声明的命名空间

#include <iostream>
#include "sample.h"

using namespace std;
using namespace N1;

void foo(void)

    cout << "b = " << b << endl;

编译时出现以下错误

$> g++ sample1.cpp sample2.cpp
/tmp/ccB25lEF.o:(.data+0x0): multiple definition of `N1::b'
/tmp/cchLecEj.o:(.data+0x0): first defined here

让我知道如何解决以及如何实施“命名空间标准”以避免此问题?

【问题讨论】:

【参考方案1】:

不是#ifndef守卫的问题。

在头文件中使用extern为:

//sample.h
namespace N1

    extern int b; //extern is MUST!

    //DONT WRITE : extern int b = 80;

然后在.cpp文件中定义为:

//sample.cpp
namespace N1

    int b = 80;  //initialization should be here

【讨论】:

【参考方案2】:

包含保护仅在编译时起作用,但错误出现在链接时。这是因为 sample.h 包含在两个编译单元中,并且在两个编译单元中都创建了一个变量 N1::b。 如果你真的想要一个变量(不是const),你必须在头文件中将它声明为extern,并在一个单独的编译单元中为它创建一个内存位置:

// sample.h
#ifndef N1
#define N1
namespace N1 
    extern int b;

#endif

// sample.cpp
#include "sample.h"
namespace N1 
    int b = 80;

【讨论】:

这也没有解决我的问题,我仍然收到类似“初始化并声明'extern';重新定义'int N1::b'”的错误 @osdevkid:这是解决方案。你也一定在做其他事情。您是否使用 extern 关键字在头文件本身中对其进行了初始化?如果你这样做了,那就错了。 @Nawaz :非常感谢,我理解并纠正了这个问题,现在它正在工作。 @osdevkid:我已经在我的解决方案中解释了这一点!【参考方案3】:

这不只是sample.h文件中缺少ifdef或pragma once的情况吗?

【讨论】:

ifndef 不能解决问题。试过了。 是的,我在头文件中使用了以下内容,#ifndef N1 #define N1 #endif ;;;;;但我仍然遇到同样的错误。【参考方案4】:

每次#include sample.h 进入 .cpp 模块时,它都会为 b 创建一个新的链接器记录,为您在链接 [1] 上提供多个定义。

int 应该在 sample.cpp 中定义,或者在其他地方定义,并且在 sample.h 中简单地 extern int b。


[1] 一些链接器会忽略这一点,您将能够正常链接,但大多数情况下它会产生错误。

【讨论】:

【参考方案5】:

您的问题是您在包含在两个单独编译单元中的头文件中定义了具有外部链接的对象。该问题与命名空间本身无关。

一种解决方案是使头文件仅包含声明(例如,见下文)并将定义放在单个源文件中。

// sample.h
namespace N1

    extern int b;


// sample.cc
namespace N1

    int b = 80;

另一个解决方案是为对象提供内部链接,虽然这意味着您有多个名为b 的对象,但这可能不是问题。例如,如果 b 应该是常量,那么这将起作用,因为 const 对象默认具有内部链接。

// sample.h
namespace N1

    const int b = 80;

【讨论】:

我已经尝试过第一种方法,但它给出了类似“初始化并声明'extern';重新定义'int N1::b'”之类的错误在你的第二种方法中,我们无法更改值,我想在外面改变那个成员的值 @osdevkid:你好像写过extern int b = 80;【参考方案6】:

如果你只想定义一个常量,试试这个:

namespace N1

    enum MyEnum
    
      b = 80
    ;

包含守卫对于几乎所有 .h 文件都是一个好主意,但在这里它们可能不是您的问题。最重要的One Definition Rule 有两个主要部分:第一个部分表示每个符号只能在每个翻译单元中定义一次(这通常意味着一个 .cpp 文件)。这就是包含保护的用途:它们防止标头被包含两次,这将导致在同一个翻译单元(= .cpp 文件)中多次定义符号(如 N::b)。

但这还不是全部。对于整个程序,某些符号(例如类、非内联函数和某些变量定义)只能声明一次。这并非不合理:假设您允许在一个翻译单元中将名为 MyInt 的 int 值定义为 40,而在另一个翻译单元中定义为 80:编译器如何知道使用哪一个?当然,您可以在每个程序中多次声明此类符号(但每个翻译单元只能一次)- 或者它们只能在声明它们的翻译单元中使用。但您不能 在多个翻译单元中定义

使用 enum 是避免在您的情况下必须将声明和定义分开的一种简单方法(因为 enum 不受第二版单一定义规则的约束),但如果您真的需要(非常量)全局int 类型的,你可以这样实现:

sample.h

namespace N1

    // The extern keyword tells the compiler that this is is only
    // a declaration and the variable is defined elsewhere.
    extern int b;

sample1.cpp

#include "sample.h"

namespace N1

    // This is the definition!
    int b = 5;


void foo()

    using namespace std;
    cout<<N1:b<<endl;

sample2.cpp

#include "sample.h"

// No need to define N1::b, since it was already defined in sample1.cpp.

void bar()

    using namespace std;
    cout<<N1:b<<endl;

【讨论】:

这与“命名空间标准”中使用的方式相同吗?无论如何它解决了我的问题。 @Nawaz:这个答案正确的。我后来添加了外部的东西,因为正确解释一切需要时间,但这里真正的答案是“使用枚举”。您可以获得更好的封装(无需编写单独的定义和声明),并且实际上可以保证将值优化为常量(尽管 const 可能会被任何体面的编译器优化)。 您之前的帖子 (enum MyEnum) 不是答案。这是另一种选择。而且他可能不需要枚举,因为枚举值是常量。 @Nawaz:我知道这一点,并且我已经明确表示只有当他需要一个常量时它才有用。然而,看看他的代码,看起来他确实需要一个常量而不是变量。在这种情况下,使用枚举是一种更简单、更安全且可能更优化的解决方案。【参考方案7】:

程序包含变量N1::b 的两个定义,而必须恰好有一个。该变量必须在标头中以extern声明,并且只能在一个源文件中定义。

【讨论】:

以上是关于C++命名空间成员访问不同文件如何? “命名空间标准”是如何实现的?的主要内容,如果未能解决你的问题,请参考以下文章

如何在另一个 C++ 命名空间内的全局命名空间中定义朋友?

C++ 命名空间 (namespace)

js单例——如何避免通过命名空间访问类成员

为啥我不能从不同命名空间中的朋友类更改类的私有成员?

C++ 中的头文件和命名空间

C++ Primer 5th笔记(chap 18 大型程序工具)命名空间特性