C 20 开始支持 Module 了。在以前C 为了解决循环依赖问题,经常会把类或者函数声明写前面,实现写后面。然后中间的代码就可以实现内部模块的内聚而互相引用。比如:
代码语言:javascript复制class foo;
void bar(foo*);
class foo {
public:
foo(){
bar(this);
}
};
那么在 Module 里怎么处理这种需求呢?其实我之前一直只是知道有这么个东西,并没有深入研究过。前段时间看到公司论坛里有同学问,就现学了并且试了一下,以下是一些记录。
问题
举个例子,比如有两个文件:
代码语言:javascript复制// file: base.ixx
module;
#include <iostream>
#include <typeinfo>
export module base;
class derived;
export class base {
public:
virtual ~base() = default;
virtual void visit(derived*) {
std::cout << "base::visit -> "<< typeid(*this).name() << std::endl;
}
};
代码语言:javascript复制// file: derived.ixx
module;
#include <iostream>
#include <typeinfo>
export module derived;
export import base;
export class derived : public base {
public:
virtual void visit(derived*) override {
std::cout << "derived::visit -> "<< typeid(*this).name() << std::endl;
}
};
程序的本意是 derived
继承 base
类。但是 derived::visit(derived*)
在多态上override了 base::visit(derived*)
。但是实际上这里在 base.ixx
里的 class derived
和 derived.ixx
里的 class derived
不是同一个类。因为他们是处于不同模块内的,作用域和可见性也都不同。
Module partitions
按目前 Modules 文档的说法,是 禁止跨模块交叉引用 的。 为了实现模块可以跨多个文件和让接口与实现隔离,可以使用 Module partitions 功能。
最新 Module partitions 的规范见 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1103r3.pdf 的 2.2 Module partitions 章节。 最早关于 Module partitions 的提案和要解决的问题可参见 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0775r0.pdf 。
简单来说 Module partitions 有几个特性:
- 使用
:
符号来分隔base模块名和partition名。 - base模块名决定了链接符号的所有权。
- 通过
export module INDETIFY
中 INDETIFY 是否包含:
来区分当前文件是一个 Module partitions 还是 Unpartitioned module 。 - Module partitions 仅能被其他同一个base模块名的 Module partitions 或者base模块本身来
imported
。
写个sample
代码语言:javascript复制// file: foo-types.ixx
module;
export module foo:types;
export class derived;
代码语言:javascript复制// file: foo-base.ixx
module;
#include <iostream>
#include <typeinfo>
export module foo:base;
import :types;
export class base {
public:
virtual ~base() = default;
virtual void visit(derived*) {
std::cout << "base::visit -> "<< typeid(*this).name() << std::endl;
}
};
代码语言:javascript复制// file: foo-derived.ixx
module;
#include <iostream>
#include <typeinfo>
export module foo:derived;
import :types;
import :base;
export class derived : public base {
public:
virtual void visit(derived*) {
std::cout << "derived::visit -> "<< typeid(*this).name() << std::endl;
}
};
代码语言:javascript复制// file: foo.ixx
module;
export module foo;
export import :types;
export import :base;
export import :derived;
代码语言:javascript复制// file: main.cpp
import foo;
int main() {
derived d;
derived* pd = &d;
base* pb = pd;
pb->visit(nullptr);
pd->visit(nullptr);
return 0;
}
执行输出结果如下:
代码语言:javascript复制derived::visit -> class derived
derived::visit -> class derived
编译命令
MSVC
代码语言:javascript复制cl /std:c latest /c /experimental:module foo-types.ixx /nologo /EHsc /MDd
cl /std:c latest /c /experimental:module foo-base.ixx /nologo /EHsc /MDd
cl /std:c latest /c /experimental:module foo-derived.ixx /nologo /EHsc /MDd
cl /std:c latest /c /experimental:module foo.ixx /nologo /EHsc /MDd
cl /std:c latest /c /experimental:module main.cpp /nologo /EHsc /MDd
cl /nologo /EHsc /MDd main.obj foo.obj foo-derived.obj foo-base.obj foo-types.obj
Clang
Clang 目前还不支持 Module partitions 功能。(我这里的版本是 Clang 11.0.0) 猜测以后支持了的话,命令应该是下面这样:
代码语言:javascript复制clang -std=c 20 -stdlib=libc -fmodules --precompile -fprebuilt-module-path=. -x c -module foo-types.ixx -o foo-types.pcm
clang -std=c 20 -stdlib=libc -fmodules --precompile -fprebuilt-module-path=. -x c -module foo-base.ixx -o foo-base.pcm
clang -std=c 20 -stdlib=libc -fmodules --precompile -fprebuilt-module-path=. -x c -module foo-derived.ixx -o foo-derived.pcm
clang -std=c 20 -stdlib=libc -fmodules --precompile -fprebuilt-module-path=. -x c -module foo.ixx -o foo.pcm
clang -std=c 20 -stdlib=libc -fmodules -fprebuilt-module-path=. main.cpp
也可能需要用 module-map-file
来辅助文件查找。
GCC
GCC 11以上才初步支持 Module 。我本地下了个snapshot的GCC( gcc version 11.0.1 20210321 (experimental) (GCC)
)。 但是GCC有BUG编不出来。可以有兴趣可以跟进一下 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99769 ,等解决了大致上就是下面这样的命令:
g -fmodules-ts -std=c 20 -x c -c foo-types.ixx -o foo-types.o
g -fmodules-ts -std=c 20 -x c -c foo-base.ixx -o foo-base.o
g -fmodules-ts -std=c 20 -x c -c foo-derived.ixx -o foo-derived.o
g -fmodules-ts -std=c 20 -x c -c foo.ixx -o foo.o
g -fmodules-ts -std=c 20 -c main.cpp foo.o foo-derived.o foo-base.o foo-types.o
参考文献
- https://www.modernescpp.com/index.php/c-20-module-interface-unit-and-module-implementation-unit
- https://zhuanlan.zhihu.com/p/350136757
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1103r3.pdf
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0775r0.pdf
- https://en.cppreference.com/w/cpp/language/modules
- https://clang.llvm.org/docs/Modules.html
- https://gcc.gnu.org/wiki/cxx-modules#Compiled_Module_Interface_Files
- https://devblogs.microsoft.com/cppblog/standard-c20-modules-support-with-msvc-in-visual-studio-2019-version-16-8/
最后
第一次上手 C Module ,可能文中有不正确的地方。欢迎指正交流。