use*_*869 3 c++ c++20 c++-modules
我有两个文件 Interface.cppm(主模块接口单元)和 main.cpp。我没有该模块的任何其他模块单元。
在Interface.cppm中,我有以下内容
module;
#include <cstdint>
export module Interface;
import <algorithm>;
import <iostream>;
import <memory>;
import <sstream>;
import <string>;
import <tuple>;
import <type_traits>;
import <vector>;
//Code that this interface exports and
//implementation details.
Run Code Online (Sandbox Code Playgroud)
我是 main.cpp,我有以下代码:
import Interface;
import <iostream>;
import <memory>;
import <string>;
int main(){
//Using the contents of Interface module
}
Run Code Online (Sandbox Code Playgroud)
我预编译了标头单元并将它们放在名为 header-units 的文件夹中。然后我使用以下命令编译我的代码:
clang++ -std=c++20 Interface.cppm -fmodule-file=./header-units/algorithm.pcm -fmodule-file=./header-units/iostream.pcm --precompile -fmodule-file=./header-units/memory.pcm -fmodule-file=./header-units/sstream.pcm -fmodule-file=./header-units/string.pcm -fmodule-file=./header-units/tuple.pcm -fmodule-file=./header-units/type_traits.pcm -fmodule-file=./header-units/vector.pcm -fmodule-file=./header-units/unordered_map.pcm -o Interface.pcm //This works fine
clang++ -std=c++20 main.cpp -fmodule-file=Interface.pcm -fmodule-file=./header-units/iostream.pcm -fmodule-file=./header-units/string.pcm -fmodule-file=./header-units/memory.pcm -c -o main.o //This works fine
clang++ -std=c++20 Interface.pcm -c -o Interface.o //This works fine
clang++ -std=c++20 Interface.o main.o -o output
Run Code Online (Sandbox Code Playgroud)
在执行最后一个命令之后,我收到一系列类似于以下内容的链接器错误:
usr/bin/ld: main.o: in function `std::bad_alloc::bad_alloc()':
main.cpp:(.text+0x0): multiple definition of `std::bad_alloc::bad_alloc()'; Interface.o:Interface.pcm:(.text+0x0): first defined here
/usr/bin/ld: main.o: in function `std::exception::exception()':
main.cpp:(.text+0x40): multiple definition of `std::exception::exception()'; Interface.o:Interface.pcm:(.text+0x40): first defined here
/usr/bin/ld: main.o: in function `std::bad_array_new_length::bad_array_new_length()':
<and many others>
Run Code Online (Sandbox Code Playgroud)
我尝试了其他方法,例如从 Interface 模块导出标头单元,而不是像这样在 main.cpp 中导入这些标头单元:
//Interface.cppm
module;
#include <cstdint>
export module Interface;
export import <iostream>;
export import <memory>;
export import <string>;
import <algorithm>;
....
//main.cpp
import Interface;
int main(){
//Code using the Interface
}
Run Code Online (Sandbox Code Playgroud)
但这具有相同的效果,即标准库组件中多个定义的链接器错误。我不确定我在这里做错了什么。如果有人能帮助我解决这个问题,那就太好了。
更新- 我设法通过这样做来摆脱这个问题(通过反复试验的方法):
//Interface.cppm
module;
#include <algorithm>
#include <cstdint>
#include <iostream>
...
export module Interface;
//Code that this interface exports and
//implementation details.
Run Code Online (Sandbox Code Playgroud)
我将所有导入更改为包含在 Interface.cppm 的全局模块片段中。
//main.cpp
import Interface;
import <iostream>;
import <memory>;
import <string>;
int main(){
//Code that uses the Interface module
}
Run Code Online (Sandbox Code Playgroud)
在 main.cpp 中,我只是将导入保留原样。
这能够很好地链接,但我仍然不确定为什么。
我试图了解当我们在全局模块片段中包含头文件时会发生什么。导入此类模块的代码会发生什么情况?
上述情况与导出导入的标头单元(如 )的模块有何不同export import <iostream>?
对于导出的标头单元,标头单元中的宏是否会影响导入此模块的代码中包含的任何标头?
此外,如果模块仅导入标头单元而不导出它,那么导入此类模块的代码会受到怎样的影响?标头单元会影响导入模块的代码吗?如果不是,为什么我的问题中的第一个代码片段会抛出如此多的链接器错误,指出标准库组件中违反了 ODR?
如果有人可以帮助我理解这一点,这将有助于我更好地理解模块。
我自己找到了为什么会出现重新定义错误的答案。
我在查看了Nathan Sidwell 的 CPPCon 视频(从时间戳 9 分 50 秒开始)后得到了答案。Nathan Sidwell 尝试将 TinyXML2 转换为使用模块,但他像我一样遇到了标准库组件的多个定义错误。
我在这里总结一下他所说的话:
通常,当头文件在同一翻译单元中多次包含时,为了避免出现多个定义错误,我们使用包含保护。
假设我们有以下文件:
//widget.h
#ifndef _WIDGET_H
#define _WIDGET_H
class Widget {...};
#endif
//foo.h
#ifndef _FOO_H
#define _FOO_H
#include "widget.h"
...
#endif
//bar.cpp
#include "widget.h"
#include "foo.h"
...
Run Code Online (Sandbox Code Playgroud)
在这种情况下,widget.h 中的 include 保护将防止 widget 类定义在与 bar.cpp 对应的翻译单元中被包含两次。
但是如果我们这样做:
//widget.h and foo.h as above
//bar.cpp
#include "widget.h"
import "foo.h";
Run Code Online (Sandbox Code Playgroud)
由于bar.cpp对应的翻译单元中类Widget的多个定义错误,代码将无法编译。这是因为标头单元(这里我们将 foo.h 作为标头单元导入)在某种意义上是不同的,包含防护对它们不起作用。
这里#include "widget.h"foo.h 内是一个问题。widget.h 中的标头保护不会阻止其内容被复制到 bar.cpp 的翻译单元中,即使它已经被 bar.cpp 直接包含,这将导致类 Widget 在此翻译单元中被定义两次,这违反了ODR。
这与我的代码中发生的情况完全相同。问题出在我的主模块接口文件 Interface.cppm 上。
我将分析在我的原始问题中导致多个定义错误的前两个代码片段,然后回答为什么它在第三个代码片段中起作用。
我的第一个片段是
//Interface.cppm
module;
#include <cstdint>
export module Interface;
import <algorithm>;
import <iostream>;
import <memory>;
import <sstream>;
import <string>;
import <tuple>;
import <type_traits>;
import <vector>;
//Code that this interface exports and
//implementation details.
//main.cpp
import Interface;
import <iostream>;
import <memory>;
import <string>;
int main(){
//Using the contents of Interface module
}
Run Code Online (Sandbox Code Playgroud)
这里 Interface.cppm 导入多个标准库头文件作为头文件单元,而 main.cpp 再次导入其中一些头文件单元。问题之一是import <sstream>和import <string>。这里头文件<sstream>有一个#include <string>,我再次导入< string>。标准库头文件<string>包括其他标准库头文件和一些内部实现头文件exception,compare等等。我得到的多个定义错误就是针对这些的。还<sstream>直接<iostream>包含常见标头,例如<ios>、<istream>和<ostream>。这些导致了另一大块重定义错误。还有其他问题,例如与<vector>和<string>都包括<initializer_list>等等。
本质上同样的问题发生在第二个代码片段中:
//Interface.cppm
module;
#include <cstdint>
export module Interface;
export import <iostream>;
export import <memory>;
export import <string>;
import <algorithm>;
....
//main.cpp
import Interface;
int main(){
//Code using the Interface
}
Run Code Online (Sandbox Code Playgroud)
这里唯一的变化是 Interface.cppm 重新导出一些导入的标头单元,以便 main 不必导入它们。但事实上 Interface.cppm 导入头单元<sstream>仍然<string>是一个问题,会导致多个重新定义错误,并且这个问题没有得到解决。
然而,在第三个片段中:
//Interface.cppm
module;
#include <algorithm>
#include <cstdint>
#include <iostream>
...
export module Interface;
//Code that this interface exports and
//implementation details.
//main.cpp
import Interface;
import <iostream>;
import <memory>;
import <string>;
int main(){
//Code that uses the Interface module
}
Run Code Online (Sandbox Code Playgroud)
没有重新定义错误。这是因为这里 Interface.cppm 不使用导入,而是使用全局模块片段中的包含,并且包含防护在这里发挥作用并防止多次包含。
然而,在 main.cpp 中,我有 3 个导入,即iostream,memory和string。
我想了解为什么这 3 个标头单元导入不会导致多个定义错误,并且我深入研究了 libc++(我正在使用的标准库)代码。
除了名为version、的文件__assert以及__config一些其他实现定义的头文件(如 )之外<__memory/allocate_at_least.h>,它们与 Interface.cppm 中的其他头单元不同,没有任何共同点。我没有直接在 main.cpp 中包含/导入任何这些文件,因此没有冲突。
现在我发现了为什么我的代码有效或为什么它不起作用,但我仍然没有回答其他问题。我还有一个与导入标头单元相关的新问题,这可能会导致多个定义错误 - 那么我应该如何解决这个问题?我将在一个新问题中问这些。
| 归档时间: |
|
| 查看次数: |
951 次 |
| 最近记录: |