15

我使用vector::emplace_back它是为了避免在填充向量时构造时间对象。这里有一个简化版本:

class Foo {
public:
    Foo(int i, double d) : i_(i), d_(d) {}
    /* ... */
};

std::vector<Foo> v;
v.reserve(10);
for (int i = 0; i < 10; i++)
    v.emplace_back(1, 1.0);

但我想std::fill_n改用:

v.reserve(10);
std::fill_n(std::back_inserter(v), 10, Foo(1, 1.0));

但是,通过这种方式,将创建临时副本。我不知道如何emplace在这种情况下使用。我想我需要类似的东西std::back_emplacer,但我找不到这样的东西。那是 C++11 的一部分,但还没有在 GCC 中实现吗?如果它不是 C++11 的一部分,还有其他方法吗?

4

6 回答 6

15

通常使用元组来简化传递可变数量的项目(在本例中是要转发到的参数emplace_back),并使用一些技巧将元组解包回来。因此,可以通过要求用户在有意义的地方back_emplacer使用元组工厂函数(其中一个std::make_tuple, std::tie, )来编写实用程序:std::forward_as_tuple

#include <type_traits>
#include <tuple>

// Reusable utilites

template<typename T>
using RemoveReference = typename std::remove_reference<T>::type;
template<typename T>
using Bare = typename std::remove_cv<RemoveReference<T>>::type;

template<typename Out, typename In>
using WithValueCategoryOf = typename std::conditional<
    std::is_lvalue_reference<In>::value
    ,  typename std::add_lvalue_reference<Out>::type
    , typename std::conditional<
        std::is_rvalue_reference<Out>::value
        , typename std::add_rvalue_reference<Out>::type
        , Out
    >::type
>::type;

template<int N, typename Tuple>
using TupleElement = WithValueCategoryOf<
    typename std::tuple_element<N, RemoveReference<Tuple>>::type
    , Tuple
>;  

// Utilities to unpack a tuple
template<int... N>
struct indices {
    using next = indices<N..., sizeof...(N)>;
};

template<int N>
struct build_indices {
    using type = typename build_indices<N - 1>::type::next;
};
template<>
struct build_indices<0> {
    using type = indices<>;
};

template<typename Tuple>
constexpr
typename build_indices<std::tuple_size<Bare<Tuple>>::value>::type
make_indices() { return {}; }

template<typename Container>
class back_emplace_iterator {
public:
    explicit back_emplace_iterator(Container& container)
        : container(&container)
    {}  

    template<
        typename Tuple
        // It's important that a member like operator= be constrained
        // in this case the constraint is delegated to emplace,
        // where it can more easily be expressed (by expanding the tuple)   
        , typename = decltype( emplace(std::declval<Tuple>(), make_indices<Tuple>()) )
    >
    back_emplace_iterator& operator=(Tuple&& tuple)
    {
        emplace(*container, std::forward<Tuple>(tuple), make_indices<Tuple>());

        return *this;
    }

    template<
        typename Tuple
        , int... Indices  
        , typename std::enable_if<
            std::is_constructible<
                typename Container::value_type
                , TupleElement<Indices, Tuple>...
            >::value
            , int
        >::type...
    >
    void emplace(Tuple&& tuple, indices<Indices...>)
    {
        using std::get;
        container->emplace_back(get<Indices>(std::forward<Tuple>(tuple))...);
    }

    // Mimic interface of std::back_insert_iterator
    back_emplace_iterator& operator*() { return *this; }
    back_emplace_iterator& operator++() { return *this; }
    back_emplace_iterator operator++(int) { return *this; }

private:
    Container* container;  
};

template<typename Container>
back_emplace_iterator<Container> back_emplacer(Container& c)
{ return back_emplace_iterator<Container> { c }; }

提供了代码演示。在您的情况下,您想致电std::fill_n(back_emplacer(v), 10, std::forward_as_tuple(1, 1.0));(std::make_tuple也是可以接受的)。您还希望使用通常的迭代器来完成该功能——我为此推荐 Boost.Iterators。

然而,我必须真正强调的是,这样的实用程序在与std::fill_n. 在您的情况下,它将保存临时的构造Foo,有利于引用元组(如果您要使用值的元组std::make_tuple)。我把它留给读者去寻找其他back_emplacer有用的算法。

于 2012-08-26T16:14:52.740 回答
10

你是对back_emplacer的,标准中没有。你可以自己完美地写一个,但是为什么?

当您调用 时emplace_back,您必须为构造函数(任何构造函数)提供参数:vec.emplace_back(1, 2)例如。但是,您不能在 C++ 中任意传递参数元组,因此back_emplacer仅限于一元构造函数。

在 的情况下fill_n,您提供一个将被复制的参数,然后两者back_inserterback_emplacer将使用相同的参数调用相同的复制构造函数。

请注意,有generategenerate_n算法来构建新元素。但同样,任何临时副本都可能会被忽略。

因此我认为对 a 的需求back_emplacer相当轻,主要是因为语言不支持多个返回值。

编辑

如果您查看下面的评论,您会意识到使用组合可以编写一种std::forward_as_tuple机制。感谢 Luc Danton 的突破。std::is_constructibleback_emplacer

于 2012-08-26T11:42:40.223 回答
6
class Foo {
public:
  Foo(int i, double d) : i_(i), d_(d) {}
};

std::vector<Foo> v;
v.reserve(10);
std::generate_n(std::back_inserter(v), 10, [&]()->Foo{ return {1, 1.0}; });

RVO 允许将函数的返回值直接省略到将要存储的位置。

虽然在逻辑上创建了一个临时文件,但实际上没有创建临时文件。如果需要,您可以访问周围范围内的所有变量来决定如何创建元素,而不仅仅是常量。

于 2014-11-03T18:42:05.097 回答
2

不会制作任何“临时副本”。会有一个临时的,你传递给的那个fill_n。它将被复制到每个值中。

即使有一个back_emplacer,你会用什么来称呼它?emplace函数家族采用构造函数参数;fill_n对象复制到迭代器中。

于 2012-08-26T11:37:50.490 回答
0

我最近向emplace_iteratorfolly 库提交了一个类和相关的实用函数。我相信它解决了原始问题并支持自动解压缩std::tuple传递给operator=.

编辑:更新链接:https ://github.com/facebook/folly/blob/master/folly/container/Iterator.h

class Widget { Widget(int, int); };

std::vector<Widget> makeWidgets(const std::vector<int>& in) {
  std::vector<Widget> out;
  std::transform(
      in.begin(),
      in.end(),
      folly::back_emplacer(out),
      [](int i) { return folly::make_emplace_args(i, i); });
  return out;
}

folly::make_emplace_args类似于std::make_tuple但会导致将其参数完美地转发给Widget构造函数。(std::make_tuple类似的可能会导致额外的副本,并且不保留左值与右值的类型。)在这个特定的例子中,std::make_tuple虽然使用会产生相同的效果。

于 2017-05-10T17:16:02.063 回答
0

我在上面看到了@LucDanton 的答案(https://stackoverflow.com/a/12131700/1032917),但我仍然看不出让代码过于复杂的意义(除了它是在 2012 年写的,但即使鉴于...)。无论如何,我发现以下代码与 Luc 的代码一样有效:

template <typename Container>
class back_emplace_iterator
{
public:
    explicit back_emplace_iterator(Container & container)
        : container(std::addressof(container))
    {}

    template <typename... Args>
    back_emplace_iterator & operator=(Args &&... args)
    {
        static_assert(std::is_constructible_v<typename Container::value_type, Args...>, "should be constructible");

        assert(container);
        container->emplace_back(std::forward<Args>(args)...);

        return *this;
    }

    // Mimic interface of std::back_insert_iterator
    back_emplace_iterator & operator*()
    {
        return *this;
    }
    back_emplace_iterator & operator++()
    {
        return *this;
    }
    back_emplace_iterator operator++(int)
    {
        return *this;
    }

private:
    Container * container;
};

template <typename Container>
back_emplace_iterator<Container> back_emplacer(Container & c)
{
    return back_emplace_iterator<Container>{c};
}

使用 C++17 中的 CTAD,您甚至可以在不显式提供模板参数的情况下摆脱back_emplacer和编写。back_emplace_iterator(my_container)

于 2018-10-08T13:44:17.597 回答