高效直接初始化std :: vector

时间:2015-07-30 15:56:35

标签: c++ c++11 initializer-list

我有一个结构,比如说

struct A {
  A(int n) : n(n) {}
  int n;
};

我想用一些元素初始化std::vector。我可以通过使用初始化列表或通过安排新元素来完成此操作:

// 1st version: 3 ctors, 3 copy ctors, 3 dtors                                           
std::vector<A> v1{1, 2, 3};

// 2nd version: 3 ctors                                                                  
std::vector<A> v2;
v2.reserve(3);
v2.emplace_back(4);
v2.emplace_back(5);
v2.emplace_back(6);

如评论所示,第一个版本调用3个构造函数,3个复制构造函数和3个析构函数。带有emplace的版本只使用3个构造函数。

问题:显然第二个版本更好,但第一个版本更简洁。我可以两全其美吗?我可以直接初始化而无需额外费用吗?

(这里是A结构的longer version,显示了发生了什么。)

2 个答案:

答案 0 :(得分:5)

由于A可以从int转换,因此您可以使用vector的范围构造函数:

auto inits = {1, 2, 3};
std::vector<A> v1{std::begin(inits), std::end(inits)};

或者在单个声明声明中(假设您可以依赖RVO):

auto v1 = [inits={1, 2, 3}] { return std::vector<A>{std::begin(inits), std::end(inits)}; }();

答案 1 :(得分:2)

扩展@ ecatmur的答案,我开发了一段代码,允许为任何类型的向量和任何构造函数调用提供非常通用的解决方案。向量的每个元素的构造函数参数存储在tuple(适当的&&&)中,然后在构建元素时完美转发。每个元素只构造一次,基本上等同于emplace_back。这种转发甚至可以构建一个仅移动类型的向量,例如unique_ptr<?>

更新,由于RVO它应该简单地构建它们。不过,不幸的是,元素类型确实需要至少一个copy-constructor或move-constructor才能看到,即使它们是实际上是由优化器跳过的。这意味着您可以构建unique_ptr但不是mutex的向量。)

auto v2 = make_vector_efficiently<A>(
         pack_for_later(1)        // 1-arg constructor of A
        ,pack_for_later(2,"two")  // 2-arg constructor of A
        ,pack_for_later(3)        // 1-arg constructor of A
        );

上面的代码将创建一个包含三个元素的vector<A>。在我的示例中,A有两个构造函数,一个以int,string作为参数。

pack_for_later构建tuple,将其参数存储为& / &&引用。然后将其转换为on对象(类型为UniquePointerThatConverts,具有所需的转换运算符,在本例中为operator A()

make_vector_efficiently内,构建了这些转换器对象的初始化列表,然后使用vectorbegin()构建end() initializer_list的T*。您可能希望这些迭代器需要具有类型vector<T>才能构造T,但迭代器指向的类型可以转换到{ {1}}。

然后构造函数使用放置new来从转换后的对象构造(copy-)。但是,感谢RVO,副本不会发生,转换器将有效地为我们做emplace_back

无论如何,任何反馈都表示赞赏。最后,除了vector之外,将其扩展到其他容器是微不足道的。

Full code on Coliru应该适用于任何C ++ 11编译器。

更详细的说明和重要功能的副本:

pack_for_later只需构建一个std::tuple。标准make_tuple不够好,因为它忽略了引用。由pack_for_later构建的元组的每个元素都是引用(&&&,视原始参数是左值还是左值而定。

template<typename ...T> 
std:: tuple<T&&...> pack_for_later(T&&... args) { 
        // this function is really just a more
        // 'honest' make_tuple - i.e. without any decay
    return std:: tuple<T&&...> (std::forward<T>(args)...);
} 

接下来,make_vector_efficiently是带来的功能。它的第一个参数是'Target'类型,我们希望创建的向量中的元素类型。 tuples的集合被转换为我们的特殊转换器类型UniquePointerThatConverts<Target>,并且矢量的构造如上所述。

template<typename Target, typename ...PackOfTuples>
auto make_vector_efficiently(PackOfTuples&&... args)
    -> std::vector<Target>
{
    auto inits = { UniquePointerThatConverts<Target>(std::forward<PackOfTuples>(args))...};
    return std::vector<Target> {std::begin(inits), std::end(inits)};
}

因为A可以有多个构造函数,并且我们希望能够使用它们中的任何一个,pack_for_later可以返回许多不同的类型(不要忘记左值和右值)。但我们需要一种类型来构建init列表。因此,我们定义了一个合适的接口:

template<typename Target>
struct ConvInterface {
    virtual Target convert_to_target_type() const = 0;
    virtual ~ConvInterface() {}
};

因此,每个元组都被make_Conv_from_tuple转换为实现此接口的对象。它实际上会将unique_ptr返回给这样的对象,然后将其存储在具有实际转换运算符的UniquePointerThatConverts中。这种类型存储在init列表中,用于初始化向量。

template<typename Target>
struct UniquePointerThatConverts {
    std:: unique_ptr<ConvInterface<Target>> p; // A pointer to an object
               // that implements the desired interface, i.e.
               // something that can convert to the desired
               // type (Target).

    template<typename Tuple>
    UniquePointerThatConverts(Tuple&& p_)
    : p ( make_Conv_from_tuple<Target>(std:: move(p_)) )
    {
        //cout << __PRETTY_FUNCTION__ << endl;
    }
    operator Target () const {
        return p->convert_to_target_type();
    }
};

当然,还有从包中构造的实际转换运算符。

template<typename Target, typename ...T>
struct Conv : public ConvInterface<Target> {
    std::tuple<T...> the_pack_of_constructor_args;
    Conv(std::tuple<T...> &&t) : the_pack_of_constructor_args(std:: move(t)) {}

    Target convert_to_target_type () const override {
        using idx = typename make_my_index_sequence<sizeof...(T)> :: type;
        return foo(idx{});
    }
    template<size_t ...i>
    Target foo(my_index_sequence<i...>) const {
        // This next line is the main line, constructs
        // something of the Target type (see the return
        // type here) by expanding the tuple.
        return {
                std:: forward
                    < typename std:: tuple_element < i , std::tuple<T...> > :: type >
                    (std:: get<i>(the_pack_of_constructor_args))
            ...
        };
    }
};