在单独的模块单元中实现时,C++20模块程序失败
在重构项目以与模块一起使用之前,我编写了一个测试项目ExImMod
,以查看是否可以像模块文档中所宣传的那样分离声明和定义。对于我的项目,我需要将声明和定义保存在单独的翻译单元(TU)中,根据模块文档,这也是可能的。我不想使用模块分区。
遗憾的是,我的测试ExImMod
项目表明它们不能完全分开,至少对于Visual Studio 2022(STD:C++LATEST)编译器(VS22)是这样。
以下是我的主要测试程序:
// ExImModMain.cpp
import FuncEnumNum;
import AStruct;
int main()
{
A a;
a.MemberFunc();
}
A的成员函数MemberFunc()
声明如下:
// AStruct.ixx
// module; // global fragment moved to AMemberFunc.cppm (Nicol Bolas)
// #include <iostream>
export module AStruct; // primary interface module
export import FuncEnumNum; // export/imports functionalities declared in FuncEnumNum.ixx and defined in MyFunc.cppm
#include "AMemberFunc.hxx" // include header declaration
,其中包含`AMemberFunc.hxx‘声明和定义:
// AMemberFunc.hxx
export struct A
{
int MemberFunc()
{
if( num == 35 ) // OK: 'num' is defined in primary interface module 'FuncEnumNum.ixx'
{
std::cout << "num is 35
"; // OK: 'cout' is included in global fragment
}
num = MyFunc(); // OK: 'MyFunc' is declared in primary interface module and defined in 'MyFunc.cppm' module unit
if( hwColors == HwColors::YELLOW ) // OK: 'hwColors' is declared in primary interface module
{
std::cout << "hwColor is YELLOW
";
}
return 44;
}
};
以下是使用函数、枚举和int函数的定义:
// AMemberFunc.hxx
export struct A
{
int MemberFunc()
{
if( num == 35 ) // OK: 'num' is defined in primary interface module 'FuncEnumNum.ixx'
{
std::cout << "num is 35
"; // OK: 'cout' is included in global fragment
}
num = MyFunc(); // OK: 'MyFunc' is declared in primary interface module and defined in 'MyFunc.cppm' module unit
if( hwColors == HwColors::YELLOW ) // OK: 'hwColors' is declared in primary interface module
{
std::cout << "hwColor is YELLOW
";
}
return 44;
}
};
此TU声明以下功能:
// FuncEnumNum.ixx
export module FuncEnumNum; // module unit
export int num { 35 }; // OK: export and direct init of 'num'
export int MyFunc(); // OK: declaration of 'MyFunc'
export enum class HwColors // OK: declaration of enum
{
YELLOW,
BROWN,
BLUE
};
export HwColors hwColors { HwColors::YELLOW }; // OK: direct init of enum
在单独的TU中定义MyFunc()
:
// MyFunc.cppm
module FuncEnumNum; // module implementation unit
int MyFunc() // OK: definition of function in module unit
{
return 33;
}
这意味着MemberFunc()
定义在主界面中,它工作得很好。但这并不能满足我的项目需求。为了测试这一点,我删除了MemberFunc()
;
// AMemberFunc.hxx
export struct A
{
int MemberFunc(); // declares 'MemberFunc'
};
并将其放入单独的TU中:
// AMemberFunc.cppm
module;
#include <iostream>
module MemberFunc; // module unit
import AStruct; // (see Nicol Bolas answer)
int MemberFunc()
{
if( num == 35 ) // OK
{
std::cout << "num is 35
"; // OK
}
num = MyFunc(); // OK
if( hwColors == HwColors::YELLOW ) OK
{
std::cout << "hwColor is YELLOW
";
}
return 44;
}
但当实现位于单独的模块中时,VS22找不到‘Num’、‘MyFunc’和‘HwColor’的声明。
我对模块的理解是,如果我像import FuncEnumNum;
中那样导入一个接口,那么它的所有声明和定义在后续模块中都应该是可见的。情况似乎并非如此。
有什么想法说明为什么这在这里不起作用?
解决方案
单个文件
我可以继续@Nicol-Bolas的精彩回答吗?在我看来(是的,这纯粹是基于意见)模块比头文件有一个好处,我们可以在代码库中删除大约50%的文件。
不应该将头文件替换为模块分区单元,而应该只拥有.cpp文件(现在使用C++20也可以导出模块)。
模块分区和一个接口单元和一个实现单元(或几个!)有一些维护开销。我肯定只有一个文件:
// primary module interface unit
export module MyModule;
import <iostream>;
export int num { 35 };
export int MyFunc()
{
return 33;
}
export enum class HwColors
{
YELLOW,
BROWN,
BLUE
};
export HwColors hwColors { HwColors::YELLOW };
export struct A
{
int MemberFunc()
{
if( num == 35 )
{
std::cout << "num is 35
";
}
num = MyFunc();
if( hwColors == HwColors::YELLOW )
{
std::cout << "hwColor is YELLOW
";
}
return 44;
}
};
多个文件
随着一个文件的增长,可以考虑将代码库划分为责任领域,并将每个责任领域放在其自己的分区文件中:
// partition
export module MyModule : FuncEnumNum;
export int num { 35 };
export int MyFunc()
{
return 33;
}
export enum class HwColors
{
YELLOW,
BROWN,
BLUE
};
export HwColors hwColors { HwColors::YELLOW };
// partition
export module MyModule : AStruct;
import :FuncEnumNum;
export struct A
{
int MemberFunc()
{
if( num == 35 )
{
std::cout << "num is 35
";
}
num = MyFunc();
if( hwColors == HwColors::YELLOW )
{
std::cout << "hwColor is YELLOW
";
}
return 44;
}
};
// primary interface unit
export module MyModule;
export import :FuncEnumNum;
export import :AStruct;
较大库的文档
遗憾的是,头文件具有重要的功能,因为它们是尚未设置自己的Wiki的项目的优秀文档来源。
如果源代码是在没有正式文档页面的情况下发布的,那么@Nicol-Bolas给出的答案是我见过的最好的。在这种情况下,我会在主模块接口单元中放置注释:
// primary module interface unit
export module MyModule;
/*
* This function does this and that.
*/
export int MyFunc();
module MyModule;
int MyFunc()
{
return 33;
}
但该文档可以放在任何地方,并与doxygen或其他类似工具一起使用。我们将不得不拭目以待,看看未来几年软件分发的最佳实践将如何演变。
没有模块分区
如果您的编译器对模块分区的支持尚未完成,或者您在应用它们时犹豫不决,源代码可以很容易地编写出来,而不需要:
// primary module interface unit
export module MyModule;
export int num { 35 };
export int MyFunc();
export enum class HwColors
{
YELLOW,
BROWN,
BLUE
};
export HwColors hwColors { HwColors::YELLOW };
export struct A
{
int MemberFunc();
};
// module implementation unit
module MyModule;
import <iostream>;
int MyFunc()
{
return 33;
}
int A::MemberFunc()
{
if( num == 35 )
{
std::cout << "num is 35
";
}
num = MyFunc();
if( hwColors == HwColors::YELLOW )
{
std::cout << "hwColor is YELLOW
";
}
return 44;
}
这是一种更传统的方法,有声明和定义的区别。模块实现单元提供后者。值得注意的是,需要在模块接口单元内部定义全局变量num
和hwColors
。如果您想亲自体验一下,我有一个here代码示例。
摘要
我们似乎有两个主要选择来构建带有模块的C++项目:
- 模块分区
- 模块实现
对于分区,我们不需要区分声明和定义,这使得代码更易于阅读和维护。如果模块分区单元变得太大,它可以被分成几个较小的分区-它们仍将是同一命名模块的一部分(应用程序的其余部分将不需要考虑)。
对于实现,我们使用更传统的C++项目结构,其中模块接口单元类似于头文件,而实现作为源文件。
相关文章