C++模块接口分区中的非导出定义是否会产生冲突?
我使用最新版Clang和C++2b模块,遇到一个困惑的行为:原本以为static且未导出的符号应私有于文件,不会冲突,但实际出现了重定义错误。我正在学习模块文件类型(主接口、实现、接口分区、内部分区)的用法,怀疑误将接口分区当作实现文件使用。
相关代码文件
文件 Mod.cc
export module TheMod; export import :Part1; export import :Part2;
文件 Part1.cc
export module TheMod:Part1; static int loog = 1; ... other exported definitions ...
文件 Part2.cc
export module TheMod:Part2; import :Part1; static int loog = 2; ... other exported definitions ...
Clang报错信息
Part2.cc:6:12: error: redefinition of 'loog' static int loog = 2; ^ Part1.cc:4:12: note: previous definition is here static int loog = 1;
构建脚本 build.sh
CXX="/Dev/llvm-project/build/bin/clang++ -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk -std=c++2b" set -e $CXX -fprebuilt-module-path=. -x c++-module Part1.cc --precompile -o TheMod-Part1.pcm $CXX -fprebuilt-module-path=. -x c++-module Part2.cc --precompile -o TheMod-Part2.pcm
注:-isysroot参数因使用macOS而添加;-x c++-module参数用于统一文件扩展名(与CMake行为一致),若使用Clang偏好的.cppm扩展名则可能无需该参数。
原因
C++模块中,接口分区(export module X:Y)属于模块的公共接口部分,所有接口分区的内容会被合并到模块的主接口单元中。而标准规定:在模块接口单元(包括接口分区)中声明的static变量,其作用域是整个模块,而非传统非模块代码中的文件作用域。
当Part2.cc导入Part1的接口分区后,两个static int loog处于同一个模块作用域内,因此触发重定义错误。
解决方案
方案1:将接口分区改为实现分区
如果这些变量不需要暴露到模块外部,把分区的export关键字去掉,将其改为实现分区:
修改Part1.cc:
module TheMod:Part1; // 去掉export static int loog = 1; ... other definitions ...
修改Part2.cc:
module TheMod:Part2; // 去掉export import :Part1; static int loog = 2; ... other definitions ...
实现分区的内容不会被纳入模块的公共接口,其中的static变量作用域限定为当前文件,不会和其他文件的同名static冲突。
方案2:用匿名命名空间包裹变量
如果必须保留接口分区(需要导出其他定义),且要让变量私有于当前文件,使用匿名命名空间包裹:
Part1.cc:
export module TheMod:Part1; namespace { int loog = 1; } ... other exported definitions ...
Part2.cc:
export module TheMod:Part2; import :Part1; namespace { int loog = 2; } ... other exported definitions ...
匿名命名空间的作用域是当前翻译单元(文件),即使在模块接口中,不同文件的匿名命名空间内的变量也不会冲突。
方案3:使用inline static(谨慎选择)
如果希望变量在模块内是单例(所有模块内代码共享同一个实例),可以用inline static:
Part1.cc:
export module TheMod:Part1; inline static int loog = 1; ... other exported definitions ...
Part2.cc:
export module TheMod:Part2; import :Part1; // 不需要再定义loog,直接使用即可 ... other exported definitions ...
但此方案会让变量成为模块内的全局实例,不符合“私有于文件”的需求,需根据实际场景选择。
内容的提问来源于stack exchange,提问作者Rob N

