C++ std::vector的高效直接初始化

C++ std::vector的高效直接初始化,c++,c++11,initializer-list,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 c

比如说,我有一个结构

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);
//第1版:3个目录,3个复制目录,3个数据目录

std::显示所发生情况的
A
结构的向量。

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

auto inits = {1, 2, 3};
std::vector<A> v1{std::begin(inits), std::end(inits)};
autoinits={1,2,3};
std::vector v1{std::begin(inits),std::end(inits)};
或者在单个声明声明中(假设您可以依赖RVO):

autov1=[inits={1,2,3}]{returnstd::vector{std::begin(inits),std::end(inits)};}();

根据@ecatmur的答案,我开发了一段代码,可以为任何类型的向量和任何构造函数调用提供非常通用的解决方案。向量的每个元素的构造函数参数存储在
元组中(视情况而定,属于
&
&&
),然后在构建元素时完美转发。每个元素只构造一次,本质上相当于
向后放置
。这种转发甚至允许构建只移动类型的向量,例如
unique\u ptr

(更新,由于RVO,它应该只在适当的位置构造它们。然而,不幸的是,元素类型确实需要至少一个复制构造函数或移动构造函数才可见,即使它们实际上被优化器跳过了。这意味着您可以构建一个
唯一\u ptr
的向量,但不能构建
互斥体

接下来,
make_vector_\u高效地
是将所有功能组合在一起的函数。它的第一个参数是“Target”类型,即我们希望创建的向量中元素的类型。
元组的集合
被转换为我们的特殊转换器类型
uniquepointerthattconverts
,向量的构造如上所述

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)};
}
因此,通过
make\u Conv\u from\u tuple
将每个元组转换为实现此接口的对象。它实际上向这样的对象返回一个
unique\u ptr
,然后将该对象存储在具有实际转换运算符的
uniquepointerthattconverts
中。正是这种类型存储在用于初始化向量的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();
    }
};
模板
结构UniquePointerThatConverts{
std::unique_ptr p;//指向对象的指针
//实现所需接口的,即。
//可以转换成所需的东西
//类型(目标)。
模板
UniquePointerThatConverts(元组和p)
:p(从元组(std::move(p))生成转换)
{
//cout::type>
(std::get(构造函数参数的包))
...
};
}
};

emplace\u back
-s的版本在调用
A
的方法方面没有额外的成本。由于您希望在末尾有大小为3的向量,因此绝对有必要调用3个构造函数
emplace_back
完美地将您的参数转发到元素的构造函数中。(我删除了刚才的一条评论-我错了。我已经忘记了初始值设定项列表在这方面的作用。)@vukong:在大括号初始化的情况下,编译器创建3个临时
A
对象来填充它传递给
std::vector
构造函数的
std::initializer\u list
,然后将这些值复制到
std::vector
中分配的
A
对象中,最后,在
std::vector
构造函数退出后,临时变量被销毁。@vukong,澄清一下。您想要的是
emplace\u back
的效率,但不必三次键入
emplace\u back
?如果你有三个以上的时间打字,那会很烦人。另外,直接初始化允许
向量
预先知道大小并分配accordingly@vukung,我以前从未真正研究过init列表的实现,但现在我更详细地研究了它们,我对它们印象不深。它们的
begin
方法返回一个
const
指针,这意味着它们不能从中移动。我假设init列表是为了从中移动而设计的!事实上,他们有很多我想改变的地方!因此,如果我使用范围构造函数,就不会涉及复制。奇怪的是,这不是直接初始化的默认行为…@vukung这是因为向量的元素类型只有一个
初始值设定项\u list
构造函数,而
初始值设定项\u list
s只对文本类型有效。转换
initializer\U list
构造函数只对从
U
转换而来的类型
T
有用,所以我想他们不会费事。干得好!我仍然需要涉猎细节,但看起来像是一个通用的解决方案。我对Coliru上的代码做了一些调整,并更新了这里的链接。缺少
my_index_sequence
的专门化,需要调用默认构造函数(即,为了
pack_for_later()
工作)。另外,我发现这对
vector
不起作用,因为RVO不允许您跳过复制构造函数或移动构造函数的需要(即使它们永远不会在RVO下调用)。很好!我有点困惑于
map
如何
放置(分段构造,[元组])
vector
等没有。也许——特别是如果这像你所说的那样容易概括的话——你可以提交一份图书馆提案吗?这似乎是一种开箱即用的东西——事实上,在其他地方也是如此。我还想知道C++17保证的拷贝省略是否可以帮助您满足visib的需求
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)...);
} 
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)};
}
template<typename Target>
struct ConvInterface {
    virtual Target convert_to_target_type() const = 0;
    virtual ~ConvInterface() {}
};
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))
            ...
        };
    }
};