经常看到使用 STL 算法的示例使用列表初始化的容器来说明,例如:
std::vector< int > v{1, 2, 3, 4};
但是当这种方法用于(重量级)类(与int
s 不同)时,它意味着对这些类进行过多的复制操作,即使它们是通过右值(移动到)传递的,因为std::initializer_list
在上面的示例中使用只提供const_iterator
s。
为了解决这个问题,我使用以下 (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 中)?