为什么 emplace_back() 不使用统一初始化?

2024-02-24

以下代码:

#include <vector>

struct S
{
    int x, y;
};

int main()
{
    std::vector<S> v;
    v.emplace_back(0, 0);
}

使用GCC编译时出现以下错误:

In file included from c++/4.7.0/i686-pc-linux-gnu/bits/c++allocator.h:34:0,
                 from c++/4.7.0/bits/allocator.h:48,
                 from c++/4.7.0/vector:62,
                 from test.cpp:1:
c++/4.7.0/ext/new_allocator.h: In instantiation of 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = S; _Args = {int, int}; _Tp = S]':
c++/4.7.0/bits/alloc_traits.h:265:4:   required from 'static typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>; typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type = void]'
c++/4.7.0/bits/alloc_traits.h:402:4:   required from 'static void std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>]'
c++/4.7.0/bits/vector.tcc:97:6:   required from 'void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int, int}; _Tp = S; _Alloc = std::allocator<S>]'
test.cpp:11:24:   required from here
c++/4.7.0/ext/new_allocator.h:110:4: error: new initializer expression list treated as compound expression [-fpermissive]
c++/4.7.0/ext/new_allocator.h:110:4: error: no matching function for call to 'S::S(int)'
c++/4.7.0/ext/new_allocator.h:110:4: note: candidates are:
test.cpp:3:8: note: S::S()
test.cpp:3:8: note:   candidate expects 0 arguments, 1 provided
test.cpp:3:8: note: constexpr S::S(const S&)
test.cpp:3:8: note:   no known conversion for argument 1 from 'int' to 'const S&'
test.cpp:3:8: note: constexpr S::S(S&&)
test.cpp:3:8: note:   no known conversion for argument 1 from 'int' to 'S&&'

建议vector正在使用常规的()构造函数语法从参数构造元素emplace_back()。为什么不vector使用{}相反,统一初始化语法,以使上面的示例有效?

在我看来,使用没有什么可失去的{}(当有构造函数时它会调用构造函数,但在没有构造函数时仍然可以工作),并且使用它更符合 C++11 的精神{}- 毕竟,整个要点uniform初始化的特点是它被统一使用——即到处使用——来初始化对象。


英雄所见略同;v) 。我提交了一份缺陷报告,并建议对这个主题的标准进行更改。

http://cplusplus.github.com/LWG/lwg-active.html#2089 http://cplusplus.github.com/LWG/lwg-active.html#2089

另外,Luc Danton 帮助我理解了其中的困难:std::allocator 中的直接初始化与统一初始化 https://stackoverflow.com/questions/7820145/direct-vs-uniform-initialization-in-stdallocator.

当 EmplaceConstructible (23.2.1 [container.requirements.general]/13) 需求用于初始化 一个对象,直接初始化发生。初始化聚合或 使用带有 emplace 的 std::initializer_list 构造函数需要命名 初始化类型并移动临时类型。这是由于 std::allocator::construct 使用直接初始化,而不是 列表初始化(有时称为“统一初始化”) 句法。

更改 std::allocator::construct 以使用列表初始化 除其他外,会优先考虑 std::initializer_list 构造函数重载,以不直观的方式破坏有效代码 无法修复的方式 - emplace_back 无法访问 构造函数被 std::initializer_list 抢占,基本上没有 重新实现push_back。

std::vector<std::vector<int>> v;
v.emplace_back(3, 4); // v[0] == {4, 4, 4}, not {3, 4} as in list-initialization

建议的折衷方案是将 SFINAE 与 std::is_constructible 一起使用, 它测试直接初始化是否格式良好。如果 is_constructible 为 false,则有替代方案 选择 std::allocator::construct 重载,使用 列表初始化。由于列表初始化总是依赖于 直接初始化,用户将看到诊断消息,就像 列表初始化(统一初始化)始终被使用, 因为直接初始化重载不会失败。

我可以看到两个极端情况暴露了该方案中的缺陷。出现一种情况 当用于 std::initializer_list 的参数满足 构造函数,例如尝试在构造函数中插入值 {3, 4} 上面的例子。解决方法是明确指定 std::initializer_list 类型,如 v.emplace_back(std::initializer_list(3, 4))。既然这样匹配 语义就像推导 std::initializer_list 一样,似乎 这里没有什么真正的问题。

另一种情况是当参数用于聚合初始化时 满足构造函数。由于聚合不能具有用户定义的 构造函数,这要求第一个非静态数据成员 聚合可以从聚合类型隐式转换,并且 初始化列表有一个元素。解决方法是 为第二个成员提供初始化程序。仍然不可能 就地构造一个只有一个非静态数据成员的聚合 从可转换类型到聚合自身类型的转换。这 看起来是一个可以接受的小洞。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 emplace_back() 不使用统一初始化? 的相关文章

随机推荐