4

经常看到使用 STL 算法的示例使用列表初始化的容器来说明,例如:

std::vector< int > v{1, 2, 3, 4};

但是当这种方法用于(重量级)(与ints 不同)时,它意味着对这些类进行过多的复制操作,即使它们是通过右值移动到)传递的,因为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 中)?

4

2 回答 2

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可以保留所有需要的内存)
  • 评估顺序保证:它是列表初始化

演示

于 2016-03-18T14:55:21.373 回答
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)};
于 2016-03-18T06:51:01.830 回答