C ++ / C ++ 11高效的方式使用初始化器列表初始化对象的静态数组/向量,并支持基于范围的 [英] C++/C++11 Efficient way to have static array/vector of objects initialized with initializer list, and supporting range-based for

查看:243
本文介绍了C ++ / C ++ 11高效的方式使用初始化器列表初始化对象的静态数组/向量,并支持基于范围的的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设您想拥有一个与类关联的预定义值/对象(const或非const)的静态数组。可能的选项是使用 std:vector std :: array (即。[]),或。例如,



在.hpp中:

  class MyClass {
public:
static const std :: vector< MyClass> vec_pre; //没有有效的方法来构造初始化列表,因为它总是使用复制控制器,即使使用std :: move
static const std :: array< MyClass,2> arr_pre; //必须指定不方便的大小
static const MyClass carr_pre []; //与C ++ 11 for-range不兼容,因为大小未定义
};

在.cpp

  const std :: vector< MyClass> MyClass :: vec_pre = {std :: move(MyClass {1,2,3}),std :: move(MyClass {4,5,6})}; //注意:这仍然使用复制构造函数
const std :: array< MyClass,2> MyClass :: arr_pre = {MyClass {1,2,3},MyClass {4,5,6}};
const ZwSColour ZwSColour :: carr_pre [] = {MyClass {1,2,3},MyClass {1,2,3}}

当写这个内容时,我选择了 std :: vector ,因为我不必指定大小,矢量类的好处,并且它似乎像现代的C ++方式做到。问题:在测试时,我注意到它会调用Move construct,但是后来仍然为每个元素调用Copy构造函数。原因是 std :: initializer_list 只允许const访问其成员,因此向量必须将它们从initializer_list复制到其自己的存储。即使它只在启动时执行一次,这是低效的,并没有看起来有一个方法,所以我看看其他选项( std :: array C-array [] )。



第二种选择是使用 std :: array ,这也是一种现代的C ++方法,它不会遇到为每个值调用复制构造函数的问题,因为它不需要创建副本不确定为什么会这样?)。 std :: array 也有一个好处,你不需要将每个值包裹在 std :: move()。然而,它有麻烦,你必须先指定大小,所以每次添加/删除元素,你必须改变大小。 这方面有办法但没有一个是理想的。正如@ Ricky65所说,你应该只能够做

  std :: array< int> arr = {1,3,3,7,0,4,2,0,3,1,4,1,5,9}}; ..., //从初始化器列表中自动推导出它的大小:) 

好的旧C风格数组[] - 这有好处,我不必指定大小,是高效的,因为它不会调用每个对象的Copy构造函数。缺点是它不是真正的现代C ++,最大的缺点是,如果你没有在.hpp头中指定数组的大小,那么C ++ 11 for-range不会工作,因为编译器抱怨


无法使用不完整类型'const MyClass []'作为范围


您可以通过指定标头中数组的大小来克服这个错误(但这不方便,并产生难以维护的代码,因为您需要在每次添加/删除时调整大小或者使用 constexpr ,并完全声明.hpp标题中的数组和值

  constexpr static MyArray my_array [] = {MyClass {1,2,3},MyClass {4,5,6}}; 



>注意:constexprwork-around只适用于POD,因此不能用于Class对象。上述示例将导致编译时错误无效使用不完整类型'MyClass'



我想尽可能写最好的现代C ++(例如,使用复制和交换成语),因此想知道为类定义静态数组的最好方法是什么...




  • ,而不必指定大小

  • ,它不需要是复制结构(如果可能,也构造为Move)


  • 应为Clang / LLVM 3.5编译/工作。
  • 必须在头文件中指定
    < ,Visual Studio 2013 Update 4 RC和GCC 4.8.1。


EDIT1:关于不能从初始化器列表中移动值的向量问题的另一篇帖子



EDIT2:关于使用std :: array的更多信息,而不需要指定大小,这也创建/使用make_array(),并提到有一个 proposal make_array()成为aa标准。原始SO链接由@Neil Kirk评论。



EDIT3:向量方法的另一个问题在这种情况下)是你不能使用 const T T 对项目进行迭代。它只允许使用 const T& (当它是 static const )和 const T& / T& (当它 static 时)。

@ Yakk的解决方案似乎是唯一的解决方案,也适用于Visual C ++ 2013 Update 4 RC。



我发现令人吃惊的是,使用最新的C ++ 11/14标准很难实现这样一个琐碎的问题。

解决方案

数据不必存储在类中。事实上,将数据存储在类的 static 成员中会泄漏实现细节。



是数据可用,并且该数据是类类型的全局数据。这不涉及披露存储详细信息:您需要公开的是存储访问详细信息。



for(:)循环数据,并以C ++ 11风格方式对其进行操作。



将数据存储在类的 .cpp 文件中的匿名命名空间中,样式数组(或 std :: array ,我不在乎)。



 命名空间详细信息{
template<
class R,
class iterator_traits,
class iterator_category,
bool is_random_access = std :: is_base_of<
std :: random_access_iterator_tag,
iterator_category
> :: value
>
struct random_access_support {};
template< class R,class iterator_traits,class iterator_category>
struct random_access_support< R,iterator_traits,iterator_category,true> {
R const * self()const {return static_cast< R const *>(this); }
template< class S>
typename iterator_traits :: reference operator [](S& s)const {
return self() - > begin()[std :: forward< S&
}
std :: size_t size()const {return self() - > end() - self() - > begin }
};
}

template< class It>
struct range:details :: random_access_support<
range< It>,
std :: iterator_traits< It>,
typename std :: iterator_traits< it> :: iterator_category
> {
using value_type = typename std :: iterator_traits< It> :: value_type;
using reference = typename std :: iterator_traits< It> :: reference;
using iterator = It;
using iterator_category = typename std :: iterator_traits< It> :: iterator_category;
using pointer = typename std :: iterator_traits< it> :: pointer;

它的begin()const {return b; }
It end()const {return e; }

bool empty()const {return b == e; }
reference front()const {return * b; }
reference back()const {return * std :: prev(e); }

范围(It s,It f):b(s),e(f){}

range()
range(range const&)= default;
range& operator =(range const&)= default;
private:
It b;它e;
};

命名空间详细信息{
template< class T>
struct array_view_helper:range< T *> {
using non_const_T = typename std :: remove_const< T> :: type;
T * data()const {return this-> begin(); }

array_view_helper(array_view_helper const&)= default;
array_view_helper():range< T *>(nullptr,nullptr){}
array_view_helper& operator =(array_view_helper const&)= default;

template< class A>
explicit operator std :: vector< non_const_T,A>()const {
return {this-> begin(),this-> end
}
std :: vector<非_const_T> as_vector()const {
return std :: vector< non_const_T>(* this);
}

template< std :: size_t N>
array_view_helper(T(& arr)[N]):range< T *>(arr + 0,arr + N){}
template< std :: size_t N&
array_view_helper(std :: array< T,N& arr):range< T *>(arr.data(),arr.data()+ N){}
template& A>
array_view_helper(std :: vector< T,A& vec):range< T *>(vec.data(),vec.data()+ vec.size()){}
array_view_helper(T * s,T * f):range< T *(s,f){}
};
}
//非常量
模板< class T>
struct array_view:details :: array_view_helper< T> {
using base = details :: array_view_helper< T> ;;

//在C ++ 11兼容编译器中使用base :: base:
template< std :: size_t N>
array_view(T(& arr)[N]):base(arr){}
template< std :: size_t N&
array_view(std :: array< T,N& arr):base(arr){}
template< class A>
array_view(std :: vector< T,A& vec):base(vec){}
array_view(T * s,T * f):base(s,f){}

//特殊方法:
array_view(array_view const&)= default;
array_view()= default;
array_view& operator =(array_view const&)= default;
};
template< class T>
struct array_view< T const>:details :: array_view_helper< const T> {
using base = details :: array_view_helper< const T> ;;

//在C ++ 11兼容编译器中使用base :: base:
template< std :: size_t N>
array_view(stst :: array< T const,N& arr):base(arr){}
array_view(T const * s,T const * f):base(s,f) {}
template< std :: size_t N>
array_view(T const(& arr)[N]):base(arr){}

//特殊方法:
array_view(array_view const&)=
array_view()= default;
array_view& operator =(array_view const&)= default;

// const T只有构造函数:
template< std :: size_t N>
array_view(std :: array< T,N> const& arr):base(arr.data(),arr.data()+ N){}
template< std :: size_t N&
array_view(std :: array< T const,N> const& arr):base(arr.data(),arr.data()+ N){}
template< class A>
array_view(std :: vector< T,A> const& vec):base(vec.data(),vec.data()+ vec.size()){}
array_view(std: :initializer_list< T> il):base(il.begin(),il.end()){}
};

这是至少一些视图类的草图。 实例



然后展示 array_view< MyClass> 作为类的一个 static 成员,它被初始化为您在中创建的数组。 cpp 档案。



range< It> 非拥有容器。一些tomfoolery是用来阻止在SFINAE级别对 size [] 的非恒定时间调用。 back()是暴露的,如果你在无效的迭代器上调用它就无法编译。



A make_range(Container)使 range< it> 更有用。



array_view< T> 是具有来自连续缓冲容器的一组构造函数的范围 ,例如C数组, std :: array s和 std :: vector s。 (实际上是一个详尽的列表)。



这很有用,因为通过 array_view 访问与访问一个数组的原始指针到第一个元素,但我们得到许多容器有很好的方法,它适用于range-for循环。一般来说,如果一个函数接受 std :: vector< T>常数& v ,你可以用一个 array_view< T> v ,它将是一个插件替换。大的例外是运算符向量,它是显式的,以避免意外分配。


Suppose you want to have a static array of pre-defined values/objects (const or non-const) associated with a class. Possible options are to use std:vector, std::array or C-style array (ie. []), or . For example,

In .hpp:

class MyClass {
public:
    static const std::vector<MyClass> vec_pre; // No efficient way to construct with initializer list, since it always uses Copy Contructor, even when using std::move
    static const std::array<MyClass, 2> arr_pre; // Have to specify size which is inconvenient
    static const MyClass carr_pre[]; // Not compatible with C++11 for-range since size is undefined
};

In .cpp

const std::vector<MyClass> MyClass::vec_pre = { std::move(MyClass{1,2,3}), std::move(MyClass{4,5,6})  }; // NOTE: This still uses copy constructor
const std::array<MyClass, 2> MyClass::arr_pre= { MyClass{1,2,3}, MyClass{4,5,6} };
const ZwSColour ZwSColour::carr_pre[] = {  MyClass{1,2,3}, MyClass{1,2,3} }

When writing this intially, I chose the std::vector since I don't have to specify the size, I get all the goodness of the vector class, and it seems like the modern C++ way to do it. PROBLEM: while testing, I noticed that it would call the Move contructor, but then still call the Copy constructor for each element. The reason for this is the std::initializer_list only allows const access to its members, and so the vector has to copy them from the initializer_list to its own storage. Even though it's only done once at startup, this is inefficient and there doesn't appear to be a way around it, so I looked at other options (std::array and C-array[]).

Second choice was to use std::array, which is also a modern C++ way, and it doesn't suffer from the problem of calling the Copy Constructor for each value since it doesn't need to create copies (Not sure why though exactly ?). std::array also has the benefit that you don't need to wrap each value in std::move(). However, it has the annoyance that you have to specify the size first, so every time you add/remove elements, you have to change the size as well. There are ways around this but none of them are ideal. As @Ricky65 states, you should just be able to do

std::array <int> arr = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 }; //automatically deduces its size from the initializer list :)

This leaves me with the last option - the good old C-style array [] - which has the benefits that I don't have to specify the size, and is efficient in that it doesn't call the Copy constructor for each object. Downsides are that it's not really modern C++, and the biggest downside is that if you don't specify the size of the array in the .hpp header, then the C++11 for-range doesn't work as the compiler complains

Cannot use incomplete type 'const MyClass []' as a range

You can overcome this error by either specifying the size of the array in the header (but this is inconvenient and produces hard-to-maintain code, as you need to adjust the size every time you add/remove items from the initializer list), or alternatively use constexpr and completely declare the array and values in the .hpp header

constexpr static MyArray my_array[] = { MyClass{1,2,3}, MyClass{4,5,6} };

NOTE: The constexpr "work-around" will only work for POD's and so cannot be used in this case for Class objects. The above example will result in a compile-time error Invalid use of incomplete type 'MyClass'

I'm trying to write best-practice modern C++ where possible (eg. using copy-and-swap idiom), and so wonder what is the best way to define static arrays for a class...

  • without having to specify the size
  • which don't need to be Copy constructed (or Move constructed either, if possible)
  • which can be used with C++ for-range
  • which don't need to be specified in the header file
  • Should compile/work for Clang/LLVM 3.5, Visual Studio 2013 Update 4 RC, and GCC 4.8.1.

EDIT1: Another post about vector problem of not being able to move values from initializer list

EDIT2: More info on using std::array without the need to specify size, which also creates/uses make_array(), and mentions that there is a proposal for make_array() to become a a standard. Original SO link courtesy of comment by @Neil Kirk.

EDIT3: Another problem with the vector method (at least in this case) is that you cannot iterate over the items using a const T or T. It only allows iteration using const T& (when it's static const) and const T&/T& (when it's static). What's the reason for this limitation ?

Descriptive Answer to solutions

@Yakk's solution appears to be the only solution, and also works on Visual C++ 2013 Update 4 RC.

I find it staggering that such a trivial issue is so difficult to implement using the latest C++11/14 standard.

解决方案

The data does not have to be stored within the class. In fact, storing the data within a static member of the class is leaking implementation details.

All you need expose is that the data is available, and that data is global to the class type. This does not involve exposing storage details: all you need to expose is storage access details.

In particular, you want to expose the ability to for(:) loop over the data, and operate on it in a C++11 style way. So expose exactly that.

Store the data in an anonymous namespace in the class's .cpp file in a C-style array (or std::array, I don't care).

Expose in the class the following:

namespace details {
  template<
    class R,
    class iterator_traits,
    class iterator_category,
    bool is_random_access=std::is_base_of<
        std::random_access_iterator_tag,
        iterator_category
    >::value
  >
  struct random_access_support {};
  template<class R, class iterator_traits, class iterator_category>
  struct random_access_support<R, iterator_traits, iterator_category, true> {
    R const* self() const { return static_cast<R const*>(this); }
    template<class S>
    typename iterator_traits::reference operator[](S&&s) const {
      return self()->begin()[std::forward<S>(s)];
    }
    std::size_t size() const { return self()->end()-self()->begin(); }
  };
}

template<class It>
struct range:details::random_access_support<
  range<It>,
  std::iterator_traits<It>,
  typename std::iterator_traits<It>::iterator_category
> {
  using value_type = typename std::iterator_traits<It>::value_type;
  using reference = typename std::iterator_traits<It>::reference;
  using iterator = It;
  using iterator_category = typename std::iterator_traits<It>::iterator_category;
  using pointer = typename std::iterator_traits<It>::pointer;

  It begin() const { return b; }
  It end() const { return e; }

  bool empty() const { return b==e; }
  reference front() const { return *b; }
  reference back() const { return *std::prev(e); }

  range( It s, It f ):b(s),e(f) {}

  range()=default;
  range(range const&)=default;
  range& operator=(range const&)=default;
private:
  It b; It e;
};

namespace details {
  template<class T>
  struct array_view_helper:range<T*> {
    using non_const_T = typename std::remove_const<T>::type;
    T* data() const { return this->begin(); }

    array_view_helper( array_view_helper const& ) = default;
    array_view_helper():range<T*>(nullptr, nullptr){}
    array_view_helper& operator=(array_view_helper const&)=default;

    template<class A>
    explicit operator std::vector<non_const_T, A>() const {
      return { this->begin(), this->end() };
    }
    std::vector<non_const_T> as_vector() const {
      return std::vector<non_const_T>(*this);
    }

    template<std::size_t N>
    array_view_helper( T(&arr)[N] ):range<T*>(arr+0, arr+N) {}
    template<std::size_t N>
    array_view_helper( std::array<T,N>&arr ):range<T*>(arr.data(), arr.data()+N) {}
    template<class A>
    array_view_helper( std::vector<T,A>&vec ):range<T*>(vec.data(), vec.data()+vec.size()) {}
    array_view_helper( T*s, T*f ):range<T*>(s,f) {}
  };
}
// non-const
template<class T>
struct array_view:details::array_view_helper<T> {
  using base = details::array_view_helper<T>;

  // using base::base in C++11 compliant compilers:
  template<std::size_t N>
  array_view( T(&arr)[N] ):base(arr) {}
  template<std::size_t N>
  array_view( std::array<T,N>&arr ):base(arr) {}
  template<class A>
  array_view( std::vector<T,A>&vec ):base(vec) {}
  array_view( T*s, T*f ):base(s,f) {}

  // special methods:
  array_view( array_view const& ) = default;
  array_view() = default;
  array_view& operator=(array_view const&)=default;
};
template<class T>
struct array_view<T const>:details::array_view_helper<const T> {
  using base = details::array_view_helper<const T>;

  // using base::base in C++11 compliant compilers:
  template<std::size_t N>
  array_view( std::array<T const,N>&arr ):base(arr) {}
  array_view( T const*s, T const*f ):base(s,f) {}
  template<std::size_t N>
  array_view( T const(&arr)[N] ):base(arr) {}

  // special methods:
  array_view( array_view const& ) = default;
  array_view() = default;
  array_view& operator=(array_view const&)=default;

  // const T only constructors:
  template<std::size_t N>
  array_view( std::array<T,N> const&arr ):base(arr.data(), arr.data()+N) {}
  template<std::size_t N>
  array_view( std::array<T const,N> const&arr ):base(arr.data(), arr.data()+N) {}
  template<class A>
  array_view( std::vector<T,A> const&vec ):base(vec.data(), vec.data()+vec.size()) {}
  array_view( std::initializer_list<T> il):base(il.begin(), il.end()) {}
};

which is at least a sketch of some view classes. live example

Then expose an array_view<MyClass> as a static member of your class, which is initialized to the array you created in the .cpp file.

range<It> is a range of iterators that acts like a non-owning container. Some tomfoolery is done to block non-constant-time calls to size or [] at the SFINAE level. back() is exposed and simply fails to compile if you call it on invalid iterators.

A make_range(Container) makes range<It> more useful.

array_view<T> is a range<T*> that has a bunch of constructors from contiguous buffer containers, like C-arrays, std::arrays and std::vectors. (actually an exhaustive list).

This is useful because access through an array_view is about as efficient as access to a raw pointer-to-first-element of an array, but we get many of the nice methods that containers have, and it works with range-for loops. In general, if a function takes a std::vector<T> const& v, you can replace it with a function that takes a array_view<T> v and it will be a drop-in replacement. The big exception is operator vector, which is explicit, to avoid accidental allocations.

这篇关于C ++ / C ++ 11高效的方式使用初始化器列表初始化对象的静态数组/向量,并支持基于范围的的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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