经常看到使用STL算法的示例用 list-initialized 容器说明,如:
std::vector< int > v{1, 2, 3, 4};
但是当这种方法用于(重量级)类时(与int
不同),它意味着对它们进行过多的复制操作,即使它们是由 rvalue传递的(移至),因为上例中使用的std::initializer_list
仅提供了const_iterator
。
要解决此问题,我使用以下(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)}};
在构造函数体中进行一些非平凡的工作通常被认为是一种不好的做法,但是在这里我集中使用了 list-initialization 的属性 - 它是严格保留的事实从右到右。
从某个特定的角度来看,这是完全错误的做法吗?除了上面提到的方法之外,这种方法的缺点是什么?是否有另一种技术可以实现当前函数参数的可预测评估顺序(在C ++ 11,C ++ 14,C ++ 1z中)?
答案 0 :(得分:1)
从某个特定的角度来看,这是完全错误的做法吗?
如果被调用的函数在复制之前最终移动,那么它就会变得不必要地难以理解并且可能会严重破坏。请记住,std::move
不会移动。它启用移动。而已。它的被叫函数最终会移动。如果它从左到右处理参数,它就会起作用。如果不是,不是。
明确说明发生了什么。
A a; A b;
using V = std::vector< A >;
A c {a};
V v = make_container< V >{std::move(a), b, std::move(c)};
答案 1 :(得分:1)
有一个更好的解决方案:
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
可以保留所需的所有内存)