如何在 C++ 中正确使用命名空间?
Posted
技术标签:
【中文标题】如何在 C++ 中正确使用命名空间?【英文标题】:How do you properly use namespaces in C++? 【发布时间】:2010-09-07 16:21:57 【问题描述】:我来自 Java 背景,使用包而不是名称空间。我习惯于将一起工作以形成完整对象的类放入包中,然后再从该包中重用它们。但现在我正在使用 C++。
如何在 C++ 中使用命名空间?您是为整个应用程序创建一个命名空间,还是为主要组件创建命名空间?如果是这样,您如何从其他命名空间中的类创建对象?
【问题讨论】:
【参考方案1】:std :: cout
前缀 std:: 表示 名称 cout 和 endl 是 在命名空间内定义 命名为标准。命名空间允许 我们要避免无意的碰撞 在我们定义的名字之间 以及这些相同名称的使用 图书馆内。所有的名字 由标准库定义 位于标准命名空间中。写标准:: cout 使用范围运算符 ( :: 运算符)说我们 想使用名称 cout 定义在 命名空间标准。 将展示一种更简单的方法 从库中访问名称。
【讨论】:
【参考方案2】:命名空间本质上是包。它们可以这样使用:
namespace MyNamespace
class MyClass
;
然后在代码中:
MyNamespace::MyClass* pClass = new MyNamespace::MyClass();
或者,如果您想始终使用特定的命名空间,您可以这样做:
using namespace MyNamespace;
MyClass* pClass = new MyClass();
编辑: 按照bernhardrusch 所说,我倾向于根本不使用“使用命名空间x”语法,我通常在实例化我的对象时明确指定命名空间(即第一个示例我展示了)。
正如您所问的below,您可以使用任意数量的命名空间。
【讨论】:
IMO 最好习惯于将std
命名空间作为符号的前缀,而不是使用 using
。所以我现在总是写std::cout
或std::string
,因为这就是我现在所说的。我永远不会只写cout
。
虽然std
确实如此,但我个人发现,当您处理较小的库时,这一点就不那么重要了。通常你可以只使用using namespace FooBario;
,尤其是当你使用库中的大量类型时。
@jkerian,我明白你的意思,但我不同意,因为(在我看来)名称冲突更有可能来自如此小的库。大多数人注意不要将类/函数命名为与 STL 中的相同。也就是说,我同意尽可能避免在头文件中使用using namespace X;
。
@LexFridman “大多数人都小心不要将类/函数命名为与 STL 中相同的名称”——这是不正确的。例如,如果我要为一些奇怪的硬件编写一些非常专业的 I/O 代码,我永远不会使用 mylibrary::endl
以外的任何东西来表示我自己的特殊换行符序列。我的意思是,为什么要发明名字?
我的编译器仍然无法识别命名空间,即使我想明确指定它并且包含声明它的文件。【参考方案3】:
Vincent Robert 的评论是正确的 How do you properly use namespaces in C++?。
使用命名空间
命名空间至少用于帮助避免名称冲突。在 Java 中,这是通过“org.domain”习语强制执行的(因为假定除了他/她自己的域名之外不会使用其他任何东西)。
在 C++ 中,您可以为模块中的所有代码提供命名空间。例如,对于一个模块 MyModule.dll,您可以为其代码命名空间 MyModule。我在其他地方看到有人使用 MyCompany::MyProject::MyModule。我想这有点矫枉过正,但总而言之,这对我来说似乎是正确的。
使用“使用”
使用时应该非常小心,因为它可以有效地将一个(或所有)符号从一个命名空间导入到您当前的命名空间中。
在头文件中这样做是邪恶的,因为您的头文件会污染包括它在内的每个源(它让我想起宏......),甚至在源文件中,函数范围之外的错误样式,因为它会导入在全局范围内来自命名空间的符号。
使用“using”最安全的方法是导入选择符号:
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 ;
你会看到很多“使用命名空间标准;”在教程或示例代码中。原因是减少符号的数量以使阅读更容易,而不是因为这是一个好主意。
"使用命名空间标准;"被 Scott Meyers 劝阻(我不记得具体是哪本书,但如果有必要我可以找到)。
命名空间组合
命名空间不仅仅是包。另一个例子可以在 Bjarne Stroustrup 的“The C++ Programming Language”中找到。
在“特别版”的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() ;
您甚至可以从不同的命名空间导入选择符号,以构建您自己的自定义命名空间接口。我还没有找到它的实际用途,但从理论上讲,它很酷。
【讨论】:
你能否澄清一下,请“给你模块中的所有代码一个命名空间”?封装到模块的好习惯是什么。例如,我有复数类和与复数相关的外部函数。这个类和这两个函数应该在一个命名空间中?【参考方案4】:java 和 C++ 的另一个区别是,在 C++ 中,命名空间层次结构不需要匹配文件系统布局。所以我倾向于将整个可重用库放在一个命名空间中,并将库中的子系统放在子目录中:
#include "lib/module1.h"
#include "lib/module2.h"
lib::class1 *v = new lib::class1();
如果存在名称冲突的可能性,我只会将子系统放在嵌套的命名空间中。
【讨论】:
【参考方案5】:另外,请注意您可以添加到命名空间。举个例子就更清楚了,我的意思是你可以拥有:
namespace MyNamespace
double square(double x) return x * x;
在文件square.h
中,并且
namespace MyNamespace
double cube(double x) return x * x * x;
在文件cube.h
中。这定义了一个命名空间MyNamespace
(也就是说,您可以跨多个文件定义一个命名空间)。
【讨论】:
【参考方案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。]
【讨论】:
请注意,std
现在也有 shared_ptr
,因此当您尝试使用 shared_ptr
时,同时使用 boost
和 std
命名空间会发生冲突。
这是一个很好的例子,说明为什么许多软件公司不鼓励以这种方式导入整个命名空间。始终指定命名空间并没有什么坏处,如果它们太长,则从命名空间中创建一个别名或仅创建重要的特定类。【参考方案7】:
为了避免说一切马克英格拉姆已经说过使用命名空间的小技巧:
避免头文件中的“使用命名空间”指令 - 这将为导入此头文件的程序的所有部分打开命名空间。在实现文件(*.cpp)中,这通常不是什么大问题——尽管我更喜欢在函数级别使用“使用命名空间”指令。
我认为命名空间主要用于避免命名冲突——不一定是为了组织你的代码结构。我主要用头文件/文件结构来组织 C++ 程序。
有时在较大的 C++ 项目中使用命名空间来隐藏实现细节。
使用指令的附加说明: 有些人更喜欢仅对单个元素使用“使用”:
using std::cout;
using std::endl;
【讨论】:
按照您的建议,在函数级别而不是在 .cpp 文件级别或命名空间 块级别“使用命名空间”的一个优点是它对单编译有很大帮助 -单位建造。 “使用命名空间”是可传递的,并且适用于同一单元中离散命名空间 A 块的命名空间 A,因此对于单编译单元构建,如果它们在文件或命名空间块级别完成,您很快就会使用所有内容。using std::cout;
是 using 声明
是否可以在单个语句中使用 single 命名空间中的 several 名称? using std::cout, std::endl;
甚至 using std::cout, endl;
之类的东西。
如果using namespace x
在另一个命名空间中,可以在标头中使用它。这不是我一般会推荐的东西,但它不会污染全局命名空间。【参考方案8】:
不要听每个人告诉你命名空间只是命名空间。
它们很重要,因为编译器会考虑它们来应用接口原则。基本上可以举例说明:
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++ 命名空间时应将其视为“接口”并牢记接口原则。
如果您想更好地解释这种行为,可以参考本书Exceptional C++ from Herb Sutter
【讨论】:
如果添加了 ns::Print,您实际上必须更改对 print() 的每个调用,但编译器会将每个调用标记为模棱两可。默默地切换到新功能将是一个糟糕的主意。 我现在想知道,如果 @Vincent 说过你将不得不更改所有对 print 的调用,如果 autor 会提供 ns::Print() 函数,你想说什么?当作者添加了一个 ns::Print() 函数时,你可以删除你自己的实现吗?或者您将使用 ns::print() using-declaration 添加?还是别的什么?谢谢【参考方案9】:我在其他答案中没有看到任何提及,所以这是我的 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;
【讨论】:
【参考方案10】:我见过的大型 C++ 项目几乎没有使用多个命名空间(例如 boost 库)。
实际上 boost 使用了大量的命名空间,通常 boost 的每个部分都有自己的命名空间用于内部工作,然后可能只将公共接口放在***命名空间 boost 中。
我个人认为代码库越大,命名空间就越重要,即使在单个应用程序(或库)中也是如此。在工作中,我们将应用程序的每个模块都放在自己的命名空间中。
我经常使用的命名空间的另一个用途(不是双关语)是匿名命名空间:
namespace
const int CONSTANT = 42;
这与以下基本相同:
static const int CONSTANT = 42;
但是,建议使用匿名命名空间(而不是静态命名空间)来使代码和数据仅在 C++ 的当前编译单元中可见。
【讨论】:
您的两个示例都等效于const int CONSTANT = 42;
,因为命名空间范围内的*** const 已经暗示了内部链接。所以在这种情况下你不需要匿名命名空间。【参考方案11】:
您还可以在函数中包含“使用命名空间...”,例如:
void test(const std::string& s)
using namespace std;
cout << s;
【讨论】:
【参考方案12】:我使用 C++ 命名空间的方式与在 C#、Perl 等中使用的相同。这只是标准库内容、第三方内容和我自己的代码之间的符号语义分离。我会将自己的应用程序放在一个命名空间中,然后将可重用的库组件放在另一个命名空间中进行分离。
【讨论】:
【参考方案13】:请注意,C++ 中的命名空间实际上只是一个命名空间。它们不提供包在 Java 中所做的任何封装,因此您可能不会经常使用它们。
【讨论】:
【参考方案14】:在 Java 中:
package somepackage;
class SomeClass
在 C++ 中:
namespace somenamespace
class SomeClass
使用它们,Java:
import somepackage;
还有 C++:
using namespace somenamespace;
此外,Java 的全名是“somepackge.SomeClass”,C++ 的全名是“somenamespace::SomeClass”。使用这些约定,您可以像在 Java 中习惯的那样进行组织,包括为命名空间创建匹配的文件夹名称。文件夹->包和文件->类的要求并不存在,所以你可以独立地命名你的文件夹和类而不是包和命名空间。
【讨论】:
【参考方案15】:我更喜欢为应用程序使用***命名空间,为组件使用子命名空间。
您可以使用来自其他名称空间的类的方式与 java 中的方式非常相似。 您可以使用类似于“import PACKAGE”语句的“use NAMESPACE”,例如使用标准。或者您将包指定为用“::”分隔的类的前缀,例如标准::字符串。这类似于Java中的“java.lang.String”。
【讨论】:
【参考方案16】:一般来说,如果我认为可能存在与其他库的函数或类型名称冲突,我会为代码体创建命名空间。它还有助于品牌代码,ala boost::。
【讨论】:
以上是关于如何在 C++ 中正确使用命名空间?的主要内容,如果未能解决你的问题,请参考以下文章
C++基础学习笔记命名空间 namespace 的理解和使用