8.1、实现分区
在模块接口分区文件中不需要声明分区,也可以在模块实现分区文件中进行声明,在一个以.cpp为扩展名的正常源代码文件中,在这种情况下,它是一个实现分区,有时候叫内部分区。这样的分区无法导出,与模块接口分区相比,它必须被模块接口主文件导出。
例如,假定有下面的math主模块接口文件(math.cppm):
export module math; // math module declarationexport namespace Math
{double superLog(double z, double b);double lerchZeta(double lambda, double alpha, double s);
}
接着假设数学函数的实现需要辅助函数必须被模块导出。实现分区是放置这样的辅助函数的绝佳之地。下面在math_helpers.cpp文件中定义了实现分区:
module math:details; // math:details implementation partitiondouble someHelperFunction(double a)
{// Implementation omitted...return 42;
}
其它的模块实现文件可以通过导入这个实现分区来访问这些辅助函数。例如,math模块实现文件(math.cpp)可能看起来像这样:
module math;import :details;double Math::superLog(double z, double b)
{// Implementation omitted...return someHelperFunction(z);
}double Math::lerchZeta(double lambda, double alpha, double s)
{// Implementation omitted...return someHelperFunction(s);
}
使用import :details;声明,superLog()与lerchZeta()函数可以调用someHelperFunction()。
当然了,使用这样的带有辅助函数的实现分区只有在多个其它源文件使用这些辅助函数时才说得通。
9、私有模块部分
模块接口主文件可以包含私有模块部分。该私有模块部分以以下行开始:
module :private;
这行之后的所有代码都是私有模块部分的代码。在这个私有模块部分定义的任何东东都不会导出,因此对模块的消费者也不可见。
在《掌握类与对象》章节中演示了pimpl习语,也叫做私有实现习语。它隐藏了类的消费者的所有实现细节。在该章节中,解决方案要求两个文件:一个主模块接口文件和一个模块实现文件。使用私有模块部分,可以通过使用一个单独的文件来达到这个分隔。下面是一个准确的例子:
export module adder;import std;export class Adder
{
public:Adder();virtual ~Adder();int add(int a, int b) const;
private:class Impl;std::unique_ptr<Impl> m_impl;public:Impl* getImplementation() { return m_impl.get(); }
};module :private;class Adder::Impl
{
public:~Impl(){std::println("Destructor of Adder::Impl");}int add(int a, int b) const{return a + b;}
};Adder::Adder(): m_impl{ std::make_unique<Impl>() }
{
}Adder::~Adder() {}int Adder::add(int a, int b) const
{return m_impl->add(a, b);
}
该类可以测试如下:
Adder adder;
println("Value: {}", adder.add(20, 22));
现在,为了证明在私有模块部分中的所有代码都被隐藏,我们添加一个公共的成员函数getImplementation()在Adder类的尾部:
export class Adder
{/* ... as before, omitted for brevity ... */
private:class Impl;std::unique_ptr<Impl> m_impl;public:Impl* getImplementation() { return m_impl.get(); }
};
下面的代码编译与运行都没有问题:
Adder adder;
auto impl { adder.getImplementation() };
从Adder模块消费者的观点来看,getImplementation()返回一个指向不完整类型的指针。该代码片断在一个叫做impl的变量中保存该指针。只要用auto类型推演,简单把指针保存在不完整类型中是没有问题的。然而,不能对该指针进行任何操作。在该不完整的指针上调用add()会产生一个错误:
auto result { impl->add(20, 22) };// Error!
这个错误有点像:使用未定义的类型Adder::Impl。原因是Adder::Impl类是私有模块部分的一部分,因此Adder模块的消费者无法访问。
如果从模块接口文件中去掉module :private;这一行,前面的代码段编译与运行就没有问题了。乍一看会很吃惊;毕竟Adder::Impl类没有显式导出。这是正确的--它没有显式导出,但是它是隐式导出的,因为Adder类被导出,Impl类在Adder类中被声明。