C++20模块:实现分区与接口分区未导出类的可见性差异
C++20模块:导出派生类访问模块链接基类成员的合规性问题
我正在借助CMake及主流编译器测试C20模块的各类使用场景,目标是使用拥有模块链接的基类的公有成员,同时导出派生类。探索中尝试了多种方案,部分可行部分不可行,现结合C20标准条款解答相关疑问。
代码分为三个文件:
libr5.ixx:主接口单元libr5_internal.ixx:libr5的分区单元main.cpp:导入libr5导出实体的主文件
固定代码与编译信息
所有示例中main.cpp保持不变:
// main.cpp import libr5; #include <iostream> int main(int argc, char *argv[]) { ClassInLibr5 libr5_class_instance {}; libr5_class_instance.int_value = 50; std::cout << libr5_class_instance.int_value << std::endl; std::cout << libr5_class_instance.double_value << std::endl; return 0; }
所有示例的编译命令保持不变:
clang++ -std=c++20 -fprebuilt-module-path=. --precompile libr5_internal.cppm clang++ -std=c++20 -fprebuilt-module-path=. --precompile -fmodule-file="libr5:internal=libr5_internal.pcm" libr5.cppm clang++ -std=c++20 -fprebuilt-module-path=. libr5.pcm libr5_internal.pcm -c clang++ -std=c++20 -fprebuilt-module-path=. main.cpp -c -o main.o clang++ main.o libr5.o libr5_internal.o
编译器信息:
clang version 18.0.0git Target: aarch64-unknown-linux-gnu Thread model: posix InstalledDir: /usr/local/bin
第一种尝试(编译通过)
// libr5.cppm export module libr5; export import :internal; export { struct ClassInLibr5: Internal { ClassInLibr5(): Internal() {} }; }
// libr5_internal.cppm export module libr5:internal; struct Internal { Internal(): int_value(0), double_value(0) {} int int_value; double double_value; };
第二种尝试(编译失败)
// libr5.cppm export module libr5; import :internal; export { struct ClassInLibr5: Internal { ClassInLibr5(): Internal() {} }; }
// libr5_internal.cppm module libr5:internal; struct Internal { Internal(): int_value(0), double_value(0) {} int int_value; double double_value; };
编译错误信息:
main.cpp:6:24: error: declaration of 'int_value' must be imported from module 'libr5' before it is required 6 | libr5_class_instance.int_value = 50; | ^ libr5_internal.cppm:5:7: note: declaration here is not visible 5 | int int_value; ...
第三种尝试(编译通过)
// libr5.cppm export module libr5; import :internal; export { struct ClassInLibr5: Internal { ClassInLibr5(): Internal() {} using Internal::int_value; using Internal::double_value; }; }
// libr5_internal.cppm module libr5:internal; struct Internal { Internal(): int_value(0), double_value(0) {} int int_value; double double_value; };
核心差异分析
- 第一种与第二种尝试的差异:第一种中
Internal类定义在接口分区单元(带export的分区),第二种则定义在实现分区单元(不带export的分区),二者均拥有模块链接,但可见范围不同。 - 第二种与第三种尝试的差异:第三种在派生类中显式使用
using声明引入基类成员。
技术疑问解答
1. 第一种与第二种尝试的差异是否属于C++20标准明确定义的合规行为?
是,完全符合C++20标准的明确规定,无未定义行为:
- 根据C++20标准[10.3.1p5],接口分区单元(
export module ...)的实体默认拥有模块链接,当主模块通过export import :internal导入该分区时,分区内的实体定义会被纳入主模块的导出接口,模块外部代码(如main.cpp)导入主模块后可直接访问这些实体的成员。因此第一种尝试编译通过是合规的。 - 实现分区单元(
module ...,无export)的实体仅在模块内部可见(标准[10.3.5p2]),即使派生类继承了该基类,模块外部代码也无法看到基类的成员定义——因为基类本身从未被导出到模块的公共接口中。因此第二种尝试的编译失败是标准预期的合规行为,并非编译器实现问题。
2. 第三种尝试是否是让第二种方案可行的合规解决办法?
是,这是符合标准规定的有效解决方式:
- 根据C++20标准[9.9p1],
using声明可以将基类的成员引入派生类的作用域。当派生类被导出时,这些被显式引入的成员会成为派生类导出接口的一部分(标准[10.3.5p3])。 - 虽然
Internal仅在模块内部可见,但派生类通过using Internal::int_value将基类成员纳入自身导出接口,模块外部代码可以通过派生类实例合法访问这些成员,因此第三种尝试的编译通过是合规的,完美解决了第二种方案的可见性问题。
内容的提问来源于stack exchange,提问作者Meng-Ze Chen
相关产品推荐
相关产品推荐

