您如何在C ++中正确使用名称空间?


231

我来自Java背景,这里使用的是包,而不是名称空间。我习惯于将可以一起工作以构成一个完整对象的类放入包中,然后在该包中重新使用它们。但是现在我正在使用C ++。

您如何在C ++中使用名称空间?您是为整个应用程序创建单个名称空间,还是为主要组件创建名称空间?如果是这样,如何从其他命名空间中的类创建对象?

Answers:


167

命名空间本质上是软件包。它们可以这样使用:

namespace MyNamespace
{
  class MyClass
  {
  };
}

然后在代码中:

MyNamespace::MyClass* pClass = new MyNamespace::MyClass();

或者,如果您希望始终使用特定的名称空间,则可以执行以下操作:

using namespace MyNamespace;

MyClass* pClass = new MyClass();

编辑:按照bernhardrusch所说,我倾向于完全不使用“使用命名空间x”语法,我通常在实例化对象时明确指定命名空间(即,我显示的第一个示例)。

并按下面的要求,您可以使用任意数量的名称空间。


25
IMO最好习惯于将std名称空间前缀为符号,而不要使用符号using。所以我总是写std::coutstd::string现在写,因为那就是我现在所说的。我永远不会只写cout
汤姆·萨维奇(

5
虽然这是非常正确的std,但是当您处理较小的库时,我个人发现这没那么重要。通常,您只能使用using namespace FooBario;,尤其是当您使用库中大量的类型时。
jkerian

4
@jkerian,我明白你的意思,但我不同意,因为(在我看来)名称冲突更可能来自恰好如此小的库。大多数人都注意不要将类/函数的名称与STL中的相同。就是说,我同意using namespace X;应尽可能避免在头文件中使用。
艾伦·图灵

12
@LexFridman“大多数人都注意不要将类/函数的名称与STL中的相同”-太不正确了。例如,如果我要为某些怪异的硬件编写一些非常专业的I / O代码,则除了mylibrary::endl代表我自己的特殊换行序列之外,我永远不会使用其他任何东西。我的意思是,为什么要发明名称?

我的编译器仍然无法识别名称空间,即使我想显式指定它并在声明它的位置包含文件。
bgenchel

116

为了避免说出一切,Mark Ingram已经说了一些使用命名空间的提示:

避免在头文件中使用“使用名称空间”指令-这将为导入该头文件的程序的所有部分打开名称空间。在实现文件(* .cpp)中,这通常不是什么大问题-尽管我更喜欢在功能级别上使用“ using namespace”指令。

我认为名称空间主要用于避免命名冲突-不一定要组织代码结构。我主要用头文件/文件结构来组织C ++程序。

有时,较大的C ++项目中使用名称空间来隐藏实现细节。

using指令的附加说明:有些人更喜欢仅对单个元素使用“ using”:

using std::cout;  
using std::endl;

2
在您建议的功能级别“而不是.cpp文件级别”或.cpp中命名空间{}块级别使用“使用名称空间”的一个优点是,它对单编译单元构建有很大帮助。“使用命名空间”是可传递的,并且适用于同一单元中离散命名空间A {}块之间的命名空间A,因此对于单一编译单元构建,如果它们在文件或命名空间块级别完成,则很快就会使用所有内容。
idij 2014年

using std::cout; 是使用声明
Konstantin

3
是否可以在单个语句中使用单个名称空间中的多个名称?类似或什至的东西。using std::cout, std::endl;using std::cout, endl;
AlQuemist '16

using namespace x如果标头位于另一个名称空间中,则可以在标头中使用。我一般不建议这样做,但它不会污染全局名称空间。
Praxeolitic

79

文森特·罗伯特(Vincent Robert)的评论正确无误,您如何在C ++中正确使用名称空间?

使用名称空间

至少使用命名空间来避免名称冲突。在Java中,这是通过“ org.domain”惯用语强制执行的(因为它假定除了自己的域名之外,不会使用其他任何东西)。

在C ++中,可以为模块中的所有代码提供名称空间。例如,对于模块MyModule.dll,可以为其代码命名空间MyModule。我在其他地方看到有人在使用MyCompany :: MyProject :: MyModule。我想这太过分了,但总的来说,对我来说似乎是正确的。

使用“使用”

使用时应格外小心,因为它可以将一个(或所有)符号从一个名称空间有效地导入您当前的名称空间。

在头文件中执行此操作是很邪恶的,因为您的头会污染包括它的每个源(它使我想起宏...),甚至在源文件中,函数作用域之外的样式也很糟糕,因为它将在全局作用域中导入命名空间中的符号。

使用“使用”的最安全方法是导入选择符号:

void doSomething()
{
   using std::string ; // string is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   std::cout << a << std::endl ;
}

void doSomethingElse()
{
   using namespace std ; // everything from std is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   cout << a << endl ;
}

您会看到很多“使用命名空间std”的信息。在教程或示例代码中。原因是减少符号数量以使阅读更容易,而不是因为它不是一个好主意。

“使用名称空间std;” 斯科特·迈耶斯(Scott Meyers)不鼓励这样做(我不记得确切是哪本书,但是如有必要,我可以找到它)。

命名空间组成

命名空间不仅仅是包。另一个示例可以在Bjarne Stroustrup的“ C ++编程语言”中找到。

在“特别版”的8.2.8命名空间组合中,他描述了如何将两个命名空间AAA和BBB合并为另一个命名空间CCC。因此,CCC成为AAA和BBB的别名:

namespace AAA
{
   void doSomething() ;
}

namespace BBB
{
   void doSomethingElse() ;
}

namespace CCC
{
   using namespace AAA ;
   using namespace BBB ;
}

void doSomethingAgain()
{
   CCC::doSomething() ;
   CCC::doSomethingElse() ;
}

您甚至可以从不同的命名空间导入选择符号,以构建自己的自定义命名空间接口。我还没有找到实际的用法,但是从理论上讲,这很酷。


您能否澄清一下,请“为模块中的所有代码提供名称空间”?封装到模块的良好做法是什么。例如,我有复数类以及与复数有关的外部函数。这个类和那两个函数应该在一个命名空间中?
yanpas

74

在其他答案中我没有看到任何提及,因此,这里是我的2加拿大分:

在“使用命名空间”主题上,有用的声明是命名空间别名,它允许您“重命名”命名空间,通常给它一个短名称。例如,代替:

Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::TheClassName foo;
Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::AnotherClassName bar;

你可以写:

namespace Shorter = Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally;
Shorter::TheClassName foo;
Shorter::AnotherClassName bar;

55

不要听每个人都告诉您名称空间只是名称空间。

它们很重要,因为编译器认为它们适用于接口原理。基本上,可以用一个例子来解释:

namespace ns {

class A
{
};

void print(A a)
{
}

}

如果要打印一个A对象,代码将是以下代码:

ns::A a;
print(a);

请注意,在调用函数时,我们没有明确提及命名空间。这是接口原理:C ++将以类型作为参数的函数视为该类型的接口的一部分,因此无需指定名称空间,因为该参数已经隐含了名称空间。

现在为什么这个原则很重要?想象一下,类A作者没有为该类提供print()函数。您将不得不自己提供一个。作为一名优秀的程序员,您将在自己的名称空间或全局名称空间中定义此函数。

namespace ns {

class A
{
};

}

void print(A a)
{
}

您的代码可以在任何需要的地方开始调用print(a)函数。现在想象一下,几年后,作者决定提供一个优于您的print()函数,因为他知道班级的内部知识,并且可以制作出比您更好的版本。

然后,C ++作者决定应使用他的print()函数的版本,而不是另一个命名空间中提供的版本,以尊重接口原理。并且print()函数的这种“升级”应该尽可能简单,这意味着您不必更改每次对print()函数的调用。这就是为什么可以在不指定C ++中名称空间的情况下调用“接口函数”(与类在同一名称空间中的函数)的原因。

这就是为什么在使用一个C ++名称空间并牢记接口原理时应将其视为“接口”的原因。

如果您想对此行为做更好的解释,可以参考Herb Sutter的《Exceptional C ++》一书。


23
如果添加了ns :: Print,实际上确实必须更改对print()的每个调用,但是编译器会将每个调用标记为模糊的。静默地切换到新功能将是一个糟糕的主意。

我现在想知道,@ Vincent所说的话,您将必须更改所有对print的调用,如果指导者提供ns :: Print()函数,您想说什么?那当作者添加一个ns :: Print()函数时,您可以删除自己的实现吗?还是只是使用ns :: print()using-declaration添加?还是其他?谢谢
Vaska el gato

36

我见过的更大的C ++项目几乎不使用多个命名空间(例如boost库)。

实际上,boost使用了大量的名称空间,通常boost的每个部分都有自己的名称空间用于内部工作,然后可能仅将公共接口放在顶级名称空间boost中。

我个人认为,即使在单个应用程序(或库)中,代码库越大,名称空间就越重要。在工作中,我们将应用程序的每个模块放在自己的名称空间中。

我经常使用的命名空间的另一种用途(无双关)是匿名命名空间:

namespace {
  const int CONSTANT = 42;
}

这基本上与以下内容相同:

static const int CONSTANT = 42;

但是,建议使用匿名名称空间(而不是静态名称空间),使代码和数据仅在C ++中的当前编译单元中可见。


13
您的两个示例都等效,const int CONSTANT = 42;因为名称空间范围中的顶级const已经暗示了内部链接。因此,在这种情况下,您不需要匿名名称空间。
sellibitze

19

另外,请注意,您可以添加到名称空间。用一个例子更清楚,我的意思是您可以拥有:

namespace MyNamespace
{
    double square(double x) { return x * x; }
}

在文件中square.h,并

namespace MyNamespace
{
    double cube(double x) { return x * x * x; }
}

在一个文件中cube.h。这定义了一个名称空间MyNamespace(即,您可以跨多个文件定义一个名称空间)。


11

在Java中:

package somepackage;
class SomeClass {}

在C ++中:

namespace somenamespace {
    class SomeClass {}
}

并使用它们,Java:

import somepackage;

和C ++:

using namespace somenamespace;

另外,全名是Java的“ somepackge.SomeClass”和C ++的“ somenamespace :: SomeClass”。使用这些约定,您可以像在Java中一样习惯进行组织,包括为名称空间创建匹配的文件夹名称。文件夹->包和文件->类要求不存在,因此您可以根据包和名称空间独立命名文件夹和类。


6

@ marius

是的,您可以一次使用多个名称空间,例如:

using namespace boost;   
using namespace std;  

shared_ptr<int> p(new int(1));   // shared_ptr belongs to boost   
cout << "cout belongs to std::" << endl;   // cout and endl are in std

[2月。2014年-(真的有那么长时间吗?):这个特殊的例子现在模棱两可,正如乔伊在下面指出的那样。Boost和std :::现在每个都有一个shared_ptr。]


2
请注意,现在std也是shared_ptr如此,因此在尝试使用时,同时使用booststd命名空间会发生冲突shared_ptr
2014年

2
这是一个很好的例子,说明了为什么许多软件公司不鼓励以这种方式导入整个名称空间。始终指定名称空间并没有什么坏处,如果它们太长,则可以从名称空间中创建别名或仅创建重要的特定类。
水稻

5

您还可以在函数内包含“使用名称空间...”,例如:

void test(const std::string& s) {
    using namespace std;
    cout << s;
}

3

一般来说,如果我认为可能与其他库存在函数或类型名称冲突,则为代码主体创建一个名称空间。它还有助于商标代码,也可以提高::


3

我更喜欢为应用程序使用顶级名称空间,为组件使用子名称空间。

令人惊讶的是,您可以使用其他命名空间中的类的方式与java中的方式非常相似。您可以使用类似于“ import PACKAGE”语句的“ use NAMESPACE”,例如,使用std。或者,您将包指定为类的前缀,并用“ ::”分隔,例如std :: string。这类似于Java中的“ java.lang.String”。


3

请注意,C ++中的名称空间实际上只是一个名称空间。它们没有提供包在Java中所做的任何封装,因此您可能不会过多地使用它们。


2

我使用C ++名称空间的方式与在C#,Perl等中使用的方式相同。这只是标准库内容,第三方内容和我自己的代码之间符号的语义分离。我将自己的应用放在一个名称空间中,然后将一个可重用的库组件放在另一个名称空间中进行分离。


2

Java和C ++之间的另一个区别是,在C ++中,名称空间层次结构不需要处理文件系统布局。因此,我倾向于将整个可重用的库放在单个名称空间中,并将库中的子系统放在子目录中:

#include "lib/module1.h"
#include "lib/module2.h"

lib::class1 *v = new lib::class1();

如果可能发生名称冲突,我只会将子系统放在嵌套的名称空间中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.