前言
想象这样一种场景:
如果A代码库提供一个接口foo来完成一些工作,突然某天由于加入了新特性,需要升级接口,而有些用户喜欢新的特性但是并不愿意为了新接口去修改他们的代码,还有部分用户认为新接口影响了稳定性,想继续使用老接口。
最直接的办法就是提供两个不同的接口函数来对应不同的版本,但是如果库中函数很多,则会出现大量需要修改的地方。
那么为了解决这种特定场景的问题,C++提供了一个新的方式来实现,叫做:内联命名空间。
命名空间
如果开发一个大工程,必然会有很多人员参与,而且还会引入很多第三方库,这导致程序中偶尔会出现函数或类型同名而冲突,这种情况下,使用命名空间就能很好的解决这个问题,通过将函数和类型纳入命名空间中,这样在不同命名空间的函数和类型就不会产生冲突,当需要使用的时候,打开其指定的命名空间即可。
比如:
namespace A {
void foo(){}
}
namespace B {
void foo(){}
}
using namespace A;
int main()
{
foo();
B::foo();
}
以上是命名空间的一个典型案例,A和B命名空间中都有foo函数,在调用两个函数时,可以通过::来指定要调用哪个命名空间中的函数。如果通过using关键字打开命名空间,则可以直接调用。
内联命名空间
看完以上的命名空间简单示例,接下来看今天的主角:内联命名空间。
一说到“内联”我们自然能想到“内联函数”,而内联命名空间同样也是使用inline来声明的。
内联命名空间能够把空间内函数和类型导出到父命名空间中,这样即使不指定子命名空间也可以使用其空间内的函数和类型。
比如:
namespace Parent {
namespace A {
void foo(){std::cout << "namespace A" << std::endl;}
}
inline namespace B {
void foo(){std::cout << "namespace B" << std::endl;}
}
}
int main()
{
Parent::A::foo();
Parent::foo();
}
在上述示例中,A不是一个内联空间,所以如果要引用A空间中的函数,必须明确指定所属命名空间,而调用B空间中的函数,则可以直接指定父命名空间即可。
看到这里,可能会有一个疑问,以上功能,直接删除内联命名空间,将foo函数直接纳入Parent命名空间不也能达到同样效果吗,干嘛要非要新整个“内联命名空间”出来。
那么,回到我们的文章标题,其实内联命名空间真正的作用,可以帮助库作者无缝升级库代码,让用户不用修改任何代码也能够自由选择新老库代码。
回顾前言中的场景:
如果Parent代码库提供一个接口foo来完成一些工作,突然某天由于加入了新特性,需要升级接口,而有些用户喜欢新的特性但是并不愿意为了新接口去修改他们的代码,还有部分用户认为新接口影响了稳定性,想继续使用老接口。
namespace Parent {
void foo(){std::cout << "foo V1.0" << std::endl;}
}
int main()
{
Parent::foo();
}
通过内联命名空间来解决这个问题:
namespace Parent {
namespace V1 {
void foo(){std::cout << "foo V1.0" << std::endl;}
}
inline namespace V2 {
void foo(){std::cout << "foo V2.0" << std::endl;}
}
}
int main()
{
Parent::foo();
}
可以看到,main中对foo的调用没有变化,但其实已经将其调用升级到了新的接口。如果后期还想用V1版本的函数,则只需要统一添加函数版本的命名空间即可。 如: Parent::V1::foo()
使用这种方式来管理接口版本非常清晰,如果想加入V3版本的接口,则只需创建V3的内联命名空间,并且将V2的inline关键字删除即可。
注意:代码中只能有一个内联命名空间,否则编译时会造成二义性问题,编译器不知道使用哪个内联命名空间的foo函数。