为啥以下代码使用clang而不是gcc编译
Posted
技术标签:
【中文标题】为啥以下代码使用clang而不是gcc编译【英文标题】:Why does the following code compile using clang but not gcc为什么以下代码使用clang而不是gcc编译 【发布时间】:2020-04-22 02:02:11 【问题描述】:#include <iostream>
#include <unordered_map>
#include <string>
struct tree_node
// tree_node() : attrib_val"null"
std::unordered_map<std::string, tree_node> child;
;
int main(int argc, char const *argv[])
return 0;
这段代码在我的 mac 上用 clang 编译得很好:
$ g++ --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/c++/4.2.1
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.7.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
$ g++ -std=c++11 test.cpp
$
在我的 linux 机器上,使用 gcc 9.1.0,我收到以下错误:
In file included from /usr/um/gcc-9.1.0/include/c++/9.1.0/bits/stl_algobase.h:64,
from /usr/um/gcc-9.1.0/include/c++/9.1.0/bits/char_traits.h:39,
from /usr/um/gcc-9.1.0/include/c++/9.1.0/ios:40,
from /usr/um/gcc-9.1.0/include/c++/9.1.0/ostream:38,
from /usr/um/gcc-9.1.0/include/c++/9.1.0/iostream:39,
from test.cpp:1:
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/stl_pair.h: In instantiation of ‘struct std::pair<const std::__cxx11::basic_string<char>, tree_node>’:
/usr/um/gcc-9.1.0/include/c++/9.1.0/ext/aligned_buffer.h:91:28: required from ‘struct __gnu_cxx::__aligned_buffer<std::pair<const std::__cxx11::basic_string<char>, tree_node> >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable_policy.h:233:43: required from ‘struct std::__detail::_Hash_node_value_base<std::pair<const std::__cxx11::basic_string<char>, tree_node> >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable_policy.h:264:12: required from ‘struct std::__detail::_Hash_node<std::pair<const std::__cxx11::basic_string<char>, tree_node>, true>’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable_policy.h:2016:13: required from ‘struct std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<const std::__cxx11::basic_string<char>, tree_node>, true> > >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable.h:173:11: required from ‘class std::_Hashtable<std::__cxx11::basic_string<char>, std::pair<const std::__cxx11::basic_string<char>, tree_node>, std::allocator<std::pair<const std::__cxx11::basic_string<char>, tree_node> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char> >, std::hash<std::__cxx11::basic_string<char> >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/unordered_map.h:105:18: required from ‘class std::unordered_map<std::__cxx11::basic_string<char>, tree_node>’
test.cpp:7:46: required from here
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/stl_pair.h:215:11: error: ‘std::pair<_T1, _T2>::second’ has incomplete type
215 | _T2 second; /// @c second is a copy of the second object
| ^~~~~~
test.cpp:5:8: note: forward declaration of ‘struct tree_node’
5 | struct tree_node
由于某种原因,它不喜欢 tree_node
作为 unordered_map
中的值。
【问题讨论】:
你确定这是在 clang 上编译的吗?不应该因为tree_node
在声明 child
时是不完整的类型
它 doesn't 在 clang 10.0 上编译。
@cigien 是的。我刚刚用我的终端输出编辑了 OP。
@cigien 另外,我不知道不完整的类型会造成问题。我在其他代码中也做过几次,不记得看到过意外行为。
您尝试过其他标准吗?如果你 man gcc 有 7 个或 8 个,你可以试试。它非常灵活。
【参考方案1】:
这是未定义的行为,[res.on.functions]/2.5
:
[如果]在实例化模板组件或评估概念时将不完整的类型 ([basic.types]) 用作模板参数,则效果未定义,除非该组件特别允许。
这是一个烦人的情况,我基本上必须证明否定的答案才能有效,但我发现标准中没有提到允许您在 std::map
中使用不完整类型的异常的地方。因此,这个程序可以做任何事情。特别是,虽然 Clang 现在编译它,但它可能在未来的任何时候停止工作,而且编译后的代码 map
特化不能正常工作也有可能。 某些容器,尤其是std::vector
,有一个子句允许它们在正确的条件下以不完整的类型实例化。但是这种情况是未定义的行为,因此编译器没有义务警告您或错误。以某种方式更改您的程序以避免这种情况。我相信以下内容是合法的,不会强迫您存储太多额外的指针。
struct tree_node
std::unique_ptr<std::unordered_map<std::string, tree_node>> child;
;
std::unique_ptr
是一般禁止规则的一个例外——可以将其实例化为不完整的类型(但它的一些成员并没有那么松懈)。我相信这意味着在tree_node
的定义中tree_node
的特化需要完成的点上,std::unordered_map<std::string, tree_node>
不需要是完整的,因此std::unordered_map
特化不会被触发并且UB被避免,因为tree_node
不需要完整。请注意,您仍然可以毫无顾虑地编写构造函数、函数、析构函数等,因为所有这些定义都隐式移出类定义并在类定义之后,并且tree_node
在类定义结束后变得完整。
【讨论】:
是的,你是对的,在这种情况下是 UB。很好的答案:) 使child
成为unique_ptr
而不是子元素的第二个值unique_ptr
有什么好处,即std::unordered_map<:string std::unique_ptr tree_node>>> 孩子?如果没有更多关于我正在使用这个结构做什么的信息,不确定这个问题是否可以回答。
老实说,一个猜测。 unordered_map
已经将其所有数据存储在堆上,位于指针后面。将tree_node
设为值类型意味着它可以将tree_node
s 直接存储在map 结构中,而不必经过更多的间接访问。这不应该被添加在结构顶部的“初始”指针完全抵消。例如。如果您对map
进行循环,我的建议希望意味着您只需取消引用“无关的”unique_ptr
一次,而您的建议绝对意味着您必须为每个元素取消引用它。
另外,我想界面更好。特别是,*child
是一个std::unordered_map<std::string, node_type>
左值,这在概念上是目标。将指针放在里面会使“不可能”。【参考方案2】:
变化:
struct tree_node
// tree_node() : attrib_val"null"
std::unordered_map<std::string, tree_node> child;
;
收件人:
struct tree_node
// tree_node() : attrib_val"null"
std::unordered_map<std::string, tree_node *> child;
;
使编译问题消失。它是否对你想要的有任何反映?
【讨论】:
这是一个简单的类型转发,不是 OP 要求的。 OP 询问为什么 clang 编译代码而 g++ 没有。上面的更改允许编译。潜在的问题可能是系统之间的地图实现不同;正如在 linux 上安装 clang 所证明的那样,并注意到它有同样的问题(如果稍微不那么精神病错误雪崩)。 也就是说,macos 实现解析为 tree_node 内不需要定义 tree_node 的结构(即使用指针);因此可以编译;而 gnu/linux 需要一个已解析的结构。它不是转发;只是指针类型不需要结构定义;只是标签。以上是关于为啥以下代码使用clang而不是gcc编译的主要内容,如果未能解决你的问题,请参考以下文章
在 MacOS 上使用 gcc-8 而不是 clang 编译 Qt5
为啥这个结构文字在 VS2013 中通过地址而不是 gcc/clang 时会损坏?