经常看到使用STL算法的示例用列表初始化的容器来说明,例如:
std::vector< int > v{1, 2, 3, 4};
但是当这种方法用于(重量级)类(与int不同)时,它意味着对它们进行过多的复制操作,即使它们是由rvalue(移动到)传递的,因为上面示例中使用的std :: initializer_list仅提供了const_iterators.
为了解决这个问题,我使用以下(C 17)方法:
template< typename Container, typename ...Args >
Container make_container(Args &&... args)
{
Container c;
(c.push_back(std::forward< Args >(args)), ...);
// ((c.insert(std::cend(c), std::forward< Args >(args)), void(0)), ...); // more generic approach
return c;
}
auto u = make_container< std::vector< A > >(A{}, A{}, A{});
但是,当我执行以下操作时,它会令人不满意:
A a;
B b;
using P = std::pair< A, B >;
auto v = make_container< std::vector< P > >(P{a, b}, P{std::move(a), std::move(b)});
这里我想通过移动操作替换复制操作来保存每个值的一个复制操作(假设移动A或B比复制便宜得多),但一般不能,因为功能评估的顺序参数在C中未定义.我目前的解决方案是:
template< Container >
struct make_container
{
template< typename ...Args >
make_container(Args &&... args)
{
(c.push_back(std::forward< Args >(args)), ...);
}
operator Container () && { return std::move(c); }
private :
Container c;
};
A a; B b;
using P = std::pair< A, B >;
using V = std::vector< P >;
V w = make_container< V >{P{a, b}, P{std::move(a), std::move(b)}};
在构造函数体中进行一些非平凡的工作通常被认为是一种不好的做法,但在这里我集中使用了列表初始化的特性 – 事实上它是严格从左到右排序的.
从某些特定的角度来看,这是完全错误的做法吗?除了上面提到的方法之外,这种方法的缺点是什么?是否存在另一种技术来实现当前功能参数的可预测评估顺序(在C 11,C 14,C 1z中)?
最佳答案 有一个更好的解决方案:
template<class Container, std::size_t N>
inline Container make_container(typename Container::value_type (&&a)[N])
{
return Container(std::make_move_iterator(std::begin(a)), std::make_move_iterator(std::end(a)));
}
你可以这样使用它:
make_container<std::vector<A>>({A(1), A(2)})
它不需要可变参数模板,它仍然是列表初始化但不是std :: initializer_list,这次它是一个普通数组,所以你可以从中移动元素.
与原始解决方案相比,显着优势:
>更好的性能:它直接调用Container的ctor,可以提供更好的性能(例如std :: vector可以保留所需的所有内存)
> Eval订单保证:它是列表初始化