从初始化列表(对于具有构造函数参数的类)的就地矢量构造 [英] in-place vector construction from initialization list (for class with constructor arguments)

查看:387
本文介绍了从初始化列表(对于具有构造函数参数的类)的就地矢量构造的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


可能重复:

我可以列表初始化一个只移动类型的向量吗?


编辑1:请考虑重新开放的投票:我的问题强调就地建设。移动施工是一个选择,但不是这个问题是什么。感谢您的答案!



编辑2:由于我不能回答这个问题(它已关闭)我在这里发表自己的建议。以下是不如我接受的答案好,但可能对其他人有用。至少只有move构造函数被调用:



  std :: vector< A2> vec; 
{
std :: array< A2,3>数字{{2,3},{5,6},{7,8}}};
vec.reserve(numbers.size());
for(auto& v:numbers)vec.emplace_back(std :: move(v));
}

原文:
$ b

在考虑这个问题的答案时:在STL向量数组中初始化类我发现我找不到一种从初始化列表中获取该向量的就地构造的方法。



<$>

现在试图更清楚,我想要这个(完全正确)初始化

p $ p> std :: vector< A2> k {{2,3},{4,5},{8,9}};

具有更类似的效果:

  std :: vector< A2> k2; 
k2.reserve(3);
k2.emplace_back(2,3);
k2.emplace_back(4,5);
k2.emplace_back(8,9);

但是,在第一种情况下, 临时插入。有没有办法避免呢?

  std:

:载体< A2> k {{2,3},{4,5},std :: move(A2 {8,9})};

但是会产生对移动构造函数的附加调用,没想到。



完整示例:

pre> #include< vector>
#include< iostream>

struct A2 {
int mk;
int mj;
A2(int k,int j):mk(k),mj(j){
std :: cout< constr for<<< this< :<< mk<< std :: endl;
}
A2(const A2& a2){
mk = a2.mk;
mj = a2.mj;
std :: cout<< copy constr for<<< this< :<< mk<< std :: endl;
}
A2(A2& a2)noexcept {
mk = std :: move(a2.mk);
mj = std :: move(a2.mj);
std :: cout<< move constr for<<< this< :<< mk<< std :: endl;
}
};

struct Ano {
Ano(){
std :: cout< constr for<< this<< std :: endl;
}
Ano(const Ano& ano){
std :: cout< copy constr for<< this<< std :: endl;
}
Ano(Ano& ano)noexcept {
std :: cout< move constr for<< this<< std :: endl;
}
};


int main(){
//这里调用构造函数和拷贝构造函数:
std :: vector< A2> k {{2,3},{4,5},std :: move(A2 {8,9})};

std :: cout<< ......<<< std :: endl;
std :: vector< A2> k2;
k2.reserve(3);
//这里(当然)只有构造函数被调用:
k2.emplace_back(2,3);
k2.emplace_back(4,5);
k2.emplace_back(8,9);

std :: cout<< ......<<< std :: endl;
//这里只调用构造函数:
std :: vector< Ano> anos(3);

}

输出:

  constr for 0xbf9fdf18:2 
constr for 0xbf9fdf20:4
constr for 0xbf9fdf0c:8
move constr for 0xbf9fdf28:8
copy constr for 0x90ed008:2
copy constr for 0x90ed010:4
copy constr for 0x90ed018:8
......
constr for 0x90ed028:2
约束0x90ed030:4
constr for 0x90ed038:8
......
constr为0x90ed048
constr为0x90ed049
constr为0x90ed04a


解决方案

列表初始化 std :: vector 与你的代码片段没有什么不同,如果 initializer_list 有一个公开的非显式构造函数或 std :: vector 接受数组引用。):

  //直接使用'initializer_list' $ b std :: vector< A2> v(别名< A2 [2],A2(2,3),A2(4,5),A2(8,9) 

这不是构建 std :: vector 可以利用的实现,真的。列表初始化是统一初始化类型的通用方式。因此,没有任何方法可以 std :: vector 任何不同于任何其他用户定义的类型。因此,使用OP中的构造是可行的。



现在,支持数组(或任何常数数组)内存的实现,这就是为什么

  std :: initializer_list< T> :: iterator 

只是

  typedef T const * iterator; 

因此移出 std :: initializer_list 也是没问题的。



现在,有没有解决方案?是的,有,这是一个相当容易的,实际上!



我们想要一个自由的函数,需要一个容器和一些元组等于数的元素你想要的地方。元组容器将容器类型的构造函数的参数。理论上很简单,在实践中很容易使用指数技巧(其中<$ c $代码中的c> indices == seq build_indices == gen_seq

$

  #include< type_traits> 
#include< tuple>
#include< utility>

template< class T>使用别名= T;
template< class T>使用RemoveRef = typename std :: remove_reference< T> :: type;

template< class C,unsigned ... Is,class Tuple>
void emplace_back_one(C& c,seq< Is ...>,Tuple&& ts){
c.emplace_back(std :: get< Is>(std :: forward< Tuple& (ts))...);
}

template< class T> using Size = std :: tuple_size< RemoveRef< T>> ;;

template< class C,class ... Tuples>
void emplace_back(C& c,Tuples&&ts ...){
c.reserve(sizeof ...(Tuples));
alias< char []> {(
emplace_back_one(c,gen_seq< std :: tuple_size< RemoveRef< Tuples>> :: value> {},std :: forward< Tuples> ))
,'0')...};
}

实例 seq gen_seq



上面的代码调用 emplace_back_one 完全 sizeof ...(Tuples) times,每次传递一个元组的顺序是传递给 emplace_back 。此代码也按从左到右的顺序排列,这意味着构造函数以与为其传递元组相同的顺序调用。 emplace_back_one 然后简单地解开包含索引技巧的元组,并将参数传递给 c.emplace_back


Possible Duplicate:
Can I list-initialize a vector of move-only type?

Edit 1: Please consider a re-open vote: My question emphasize in-place construction. Move construction is an alternative but not what this questions is about. Thanks for the answers!

Edit 2: Since I can't answer this question (It got closed) I post my own suggestion here. The following is not as good as the answers I accepted, but may be useful for others. At least only the move constructor is called:

std::vector<A2> vec;
{
  std::array<A2,3> numbers{{{2,3},{5,6},{7,8}}};
  vec.reserve(numbers.size());
  for (auto &v: numbers) vec.emplace_back(std::move(v)) ;
}

Original post:

When thinking about the answer to this question: Initialization of classes within an STL array of vectors I found that I could not find a way to get in-place construction of the vector from an initialization list. What am I missing?

Now trying to be more clear, I would like this (perfectly correct) initialization

std::vector<A2> k{{2,3},{4,5},{8,9}};

to have an effect more similar to this:

  std::vector<A2> k2;
  k2.reserve(3);
  k2.emplace_back(2,3);
  k2.emplace_back(4,5);
  k2.emplace_back(8,9);

However, in the first case the copy constructor is called for A2 on a temporary while inserting. Is there a way to avoid that? What does the standard say?

I desperately tried

std::vector<A2> k{{2,3},{4,5},std::move(A2{8,9})};

but that generates an additional call to the move constructor, something I also did not expect. I just wanted to explicitly hint that A2 is a temporary, something I had thought was implied.

Full example:

#include <vector>
#include <iostream>

struct A2 {
  int mk;
  int mj;
  A2(int k,int j) : mk(k),mj(j) {
    std::cout << "     constr for "<<this<< ":"<< mk<<std::endl;
  }
  A2(const A2& a2) {
    mk=a2.mk;
    mj=a2.mj;    
    std::cout << "copy constr for "<<this<< ":" << mk<<std::endl;
  }
  A2(A2&& a2) noexcept  {
    mk=std::move(a2.mk);
    mj=std::move(a2.mj);
    std::cout << "move constr for "<<this<< ":"<< mk<<std::endl;
  }
};

struct Ano {
  Ano() {
    std::cout << "     constr for "<<this <<std::endl;
  }
  Ano(const Ano& ano) {
    std::cout << "copy constr for "<<this<<std::endl;
  }
  Ano(Ano&& ano) noexcept  {
    std::cout << "move constr for "<<this<<std::endl;
  }
};


int main (){
  // here both constructor and copy constructor is called:
  std::vector<A2> k{{2,3},{4,5},std::move(A2{8,9})};

  std::cout << "......"<<std::endl;
  std::vector<A2> k2;
  k2.reserve(3);
  // here (naturally) only constructor is called:
  k2.emplace_back(2,3);
  k2.emplace_back(4,5);
  k2.emplace_back(8,9);

  std::cout << "......"<<std::endl;  
  // here only constructor is called:
  std::vector<Ano> anos(3);

}

Output:

     constr for 0xbf9fdf18:2
     constr for 0xbf9fdf20:4
     constr for 0xbf9fdf0c:8
move constr for 0xbf9fdf28:8
copy constr for 0x90ed008:2
copy constr for 0x90ed010:4
copy constr for 0x90ed018:8
......
     constr for 0x90ed028:2
     constr for 0x90ed030:4
     constr for 0x90ed038:8
......
     constr for 0x90ed048
     constr for 0x90ed049
     constr for 0x90ed04a

解决方案

List-initializing std::vector in your snippet is no different from doing the following (if initializer_list had a public non-explicit constructor or std::vector accepted an array reference.):

// directly construct with the backing array of 'initializer_list'
std::vector<A2> v(alias<A2[]>{ A2(2,3), A2(4,5), A2(8,9) });

It's not a special way to construct a std::vector that could take advantage of the implementation, really. List-initialization is a general-purpose way of "uniformly" initializing types. As such, there's no way it could tread std::vector any different than any other user-defined type. As such, having the construct in the OP do emplace construction is out of question.

Now, the backing array (or any constant array) may be put in read-only memory by the implementation, that's the reason why

std::initializer_list<T>::iterator

is just

typedef T const* iterator;

So moving out of std::initializer_list is also out of question.

Now, is there a solution? Yes, there is, and it's a rather easy one, actually!

We will want to have a free function that takes a container and a number of tuples equal to the number of elements you want to emplace. The tuples container the arguments to the constructor of the container type. Easy in theory, easy in practice with the indices trick (where indices == seq and build_indices == gen_seq in the code):

#include <type_traits>
#include <tuple>
#include <utility>

template<class T> using alias = T;
template<class T> using RemoveRef = typename std::remove_reference<T>::type;

template<class C, unsigned... Is, class Tuple>
void emplace_back_one(C& c, seq<Is...>, Tuple&& ts){
  c.emplace_back(std::get<Is>(std::forward<Tuple>(ts))...);
}

template<class T> using Size = std::tuple_size<RemoveRef<T>>;

template<class C, class... Tuples>
void emplace_back(C& c, Tuples&&... ts){
  c.reserve(sizeof...(Tuples));
  alias<char[]>{(
    emplace_back_one(c, gen_seq<std::tuple_size<RemoveRef<Tuples>>::value>{}, std::forward<Tuples>(ts))
  , '0')...};
}

Live example with the implementation of seq and gen_seq.

The code above calls emplace_back_one exactly sizeof...(Tuples) times, passing one tuple at a time in the order that the were passed to emplace_back. This code is also sequenced left-to-right, meaning that the constructors get called in the same order that you passed the tuples for them. emplace_back_one then simply unpacks the tuple with the indices trick and passes the arguments to c.emplace_back.

这篇关于从初始化列表(对于具有构造函数参数的类)的就地矢量构造的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆