C++/C++11 Efficient way to have static array/vector of objects initialized with initializer list, and supporting range-based for

后端 未结 3 435
北恋
北恋 2020-12-15 07:29

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,

相关标签:
3条回答
  • 2020-12-15 08:03

    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.

    0 讨论(0)
  • 2020-12-15 08:04

    Here is a way to set up the vector without copies or moves.

    It doesn't use a braced initializer but your opening paragraph suggests that your main concern is avoiding copies and moves; rather than an absolute requirement to use a braced initializer.

     // header
    const std::vector<MyClass> &get_vec();
    
    // cpp file
    const std::vector<MyClass> &get_vec()
    {
        static std::vector<MyClass> x;
    
        if ( x.empty() )
        {
            x.emplace_back(1,2,3);
            x.emplace_back(4,5,6);
            // etc.
        }
    
        return x;    
    }
    
    0 讨论(0)
  • 2020-12-15 08:19

    I personally like your constexpr static int my_array[] = {MyClass{1, 2, 3}, MyClass{1, 2, 3}}; I don't think you should shy away from that if a C-style array meets your needs.

    If you really want to use std::vector though you could use static const std::vector<MyClass*> vec_pre;. So your .cpp file would have this at the top:

    namespace{
        MyClass A{1, 2, 3}, B{1, 2, 3}, C{1, 2, 3};
    }
    const std::vector<MyClass*> MyClass::vec_pre{&A, &B, &C};
    

    EDIT after DarkMatter's comments:

    After reading your comments, it looks like there could be some maintainability hazard to my method. It could still be accomplished like this in your .cpp:

    namespace{
        MyClass temp[]{MyClass{1, 2, 3}, MyClass{1, 2, 3}, MyClass{1, 2, 3}};
        const MyClass* pTemp[]{&temp[0], &temp[1], &temp[2]};
    }
    const std::vector<MyClass*> MyClass::vec_pre{begin(pTemp), end{pTemp}};
    

    You could also remove the duplication of entry maintainability problem by creating a macro to do that for you.

    0 讨论(0)
提交回复
热议问题