题
在重构一些旧代码时,我删除了许多实际上应该是静态的公共方法,因为它们a)不操作任何成员数据或调用任何其他成员函数,b)因为它们可能在其他地方有用。
这让我思考将“辅助”功能组合在一起的最佳方法。Java/C# 方法是使用带有私有构造函数的静态函数类,例如:
class Helper
{
private:
Helper() { }
public:
static int HelperFunc1();
static int HelperFunc2();
};
然而,作为 C++,你也可以使用命名空间:
namespace Helper
{
int HelperFunc1();
int HelperFunc2();
}
在大多数情况下,我认为我更喜欢命名空间方法,但我想知道每种方法的优缺点是什么。例如,如果使用类方法,是否会有任何开销?
解决方案
开销不是问题,但命名空间有一些优点
- 您可以在另一个标题中重新打开一个名称空间,在保持编译依赖性较低的同时对事物进行分组。
您可以使用命名空间的混叠来提高自己的优势(调试/发布,平台特定的助手,....)
例如我做过类似的事情
namespace LittleEndianHelper { void Function(); } namespace BigEndianHelper { void Function(); } #if powerpc namespace Helper = BigEndianHelper; #elif intel namespace Helper = LittleEndianHelper; #endif
其他提示
一种可能使用的情况 class
(或者 struct
) 超过 namespace
是当一个人需要一种类型时,例如:
struct C {
static int f() { return 33; }
};
namespace N {
int f() { return 9; }
}
template<typename T>
int foo() {
return T::f();
}
int main() {
int ret = foo<C>();
//ret += foo<N>(); // compile error: N is a namespace
return ret;
}
为了补充 Pieter 的出色回应,命名空间的另一个优点是您可以转发声明放在其他地方的命名空间中的内容,尤其是结构......
//Header a.h
// Lots of big header files, spreading throughout your code
class foo
{
struct bar {/* ... */);
};
//header b.h
#include a.h // Required, no way around it, pulls in big headers
class b
{
//...
DoSomething(foo::bar);
};
以及命名空间...
//Header a.h
// Big header files
namespace foo
{
struct bar {/* ... */);
}
//header b.h
// Avoid include, instead forward declare
// (can put forward declares in a _fwd.h file)
namespace foo
{
struct bar;
}
class b
{
//...
// note that foo:bar must be passed by reference or pointer
void DoSomething(const foo::bar & o);
};
一旦您最终得到一个跨越数百个源文件的项目,在进行小的标头更改后,前向声明会对您的编译时间产生很大的影响。
编辑自 paercebal
答案太好了,不能因为枚举错误而让它死掉(请参阅评论)。我用结构替换了枚举(只能在 C++0x 中前向声明,而不能在今天的 C++ 中前向声明)。
使用名称空间的主要优点是您可以重新打开它并稍后添加更多内容,而使用类则无法做到这一点。这使得这种方法更适合松散耦合的助手(例如,您可以为整个库拥有一个助手命名空间,就像所有 STL 都在 ::std 中一样)
类的主要优点是您可以使用它将其嵌套在类中,但不能在类中嵌套命名空间。这使得这种方法更适合紧密耦合的助手。
将它们放在类中而不是命名空间中,您不会有任何额外的开销。
命名空间提供了 Koenig 查找的额外优势。使用帮助程序类可能会使您的代码更加冗长 - 您通常需要在调用中包含帮助程序类名称。
命名空间的另一个好处是以后的可读性。对于类,您需要包含“Helper”之类的单词,以便稍后提醒您特定的类不用于创建对象
实际上,两者都没有开销。编译后,只有使用的名称 mangling 不同。
复制/修剪/修改了我的部分答案 如何在 C++ 中正确使用命名空间?.
使用“使用”
您可以使用“using”来避免重复辅助函数的“前缀”。例如:
struct AAA
{
void makeSomething() ;
} ;
namespace BBB
{
void makeSomethingElse() ;
}
void willCompile()
{
AAA::makeSomething() ;
BBB::makeSomethingElse() ;
}
void willCompileAgain()
{
using BBB ;
makeSomethingElse() ; // This will call BBB::makeSomethingElse()
}
void WONT_COMPILE()
{
using AAA ; // ERROR : Won't compile
makeSomething() ; // ERROR : Won't compile
}
命名空间组成
命名空间不仅仅是包。另一个例子可以在 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() ;
}
您甚至可以从不同的命名空间导入选择的符号,以构建您自己的自定义命名空间接口。我还没有找到它的实际用途,但从理论上讲,它很酷。
在创建辅助函数时,我倾向于使用匿名名称空间。由于它们(通常)应该仅被关心它们的模块看到,因此这是控制依赖关系的好方法。