С++/С++ 11 Эффективный способ создания статического массива/вектора объектов, инициализированных списком инициализаторов, и поддержки диапазона для
Предположим, вы хотите иметь статический массив предварительно определенных значений/объектов (const или non-const), связанных с классом. Возможные варианты использования std:vector
, std::array
или C-style array (ie. [])
, или. Например,
В .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
};
В .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} }
При написании этого я выбрал std::vector
, так как мне не нужно указывать размер, я получаю всю доброту векторного класса, и это похоже на современный С++ способ сделать это. ПРОБЛЕМА: во время тестирования я заметил, что он вызовет конструктор Move, но затем все равно вызовет конструктор Copy для каждого элемента. Причиной этого является std::initializer_list
только разрешает доступ к const своим членам, поэтому вектор должен скопировать их из списка initializer_list в собственное хранилище. Несмотря на то, что это было сделано только один раз при запуске, , это неэффективно и, похоже, не существует способа, поэтому я просмотрел другие параметры (std::array
и C-array[]
).
Второй выбор заключался в использовании std::array
, который также является современным С++-способом, и он не страдает проблемой вызова Copy Constructor для каждого значения, поскольку ему не нужно создавать копии (не знаете, почему хотя точно?). std::array
также имеет то преимущество, что вам не нужно обертывать каждое значение в std::move()
. Тем не менее, у вас есть досада, что вы должны сначала указать размер, поэтому каждый раз, когда вы добавляете/удаляете элементы, вы также должны изменять размер. Есть способы обойти это, но ни один из них не идеален. Как утверждает @Ricky65, вы должны просто иметь возможность делать
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 :)
Это оставляет мне последний вариант - старый старый массив C-style [], который имеет те преимущества, которые мне не нужно указывать, и эффективен тем, что он не вызывает конструктор Copy для каждый объект. Недостатки в том, что это не очень современный С++, и самым большим недостатком является то, что если вы не укажете размер массива в заголовке .hpp, тогда С++ 11 для диапазона не будет работать, поскольку компилятор жалуется
Нельзя использовать неполный тип 'const MyClass []' как диапазон
Вы можете преодолеть эту ошибку, указав размер массива в заголовке (но это неудобно и создает сложный код, так как вам нужно настроить размер каждый раз, когда вы добавляете/удаляете элементы из инициализатора список), или альтернативно использовать constexpr
и полностью объявить массив и значения в заголовке .hpp
constexpr static MyArray my_array[] = { MyClass{1,2,3}, MyClass{4,5,6} };
ПРИМЕЧАНИЕ. Консольс "work-around" работает только для POD и поэтому не может использоваться в этом случае для объектов класса. Приведенный выше пример приведет к ошибке времени компиляции Invalid use of incomplete type 'MyClass'
Я пытаюсь писать лучшие на практике С++, где это возможно (например, используя идиому копирования и свопинга), и поэтому задайтесь вопросом, что является лучшим способом для определения статических массивов для класса...
- без указания размера
- который не обязательно должен быть скопирован (или Move сконструирован либо, если это возможно)
- который может использоваться с С++ для диапазона
- которые не обязательно должны указываться в файле заголовка
- Должен компилироваться/работать для Clang/LLVM 3.5, Visual Studio 2013 Update 4 RC и GCC 4.8.1.
EDIT1: Еще одно сообщение об векторной проблеме невозможности перемещения значений из списка инициализаторов
EDIT2: Дополнительная информация об использовании std:: массива без указания размера, который также создает/использует make_array() и упоминает, что существует предложение для make_array(), чтобы стать стандартом. Оригинальная ссылка SO, предоставленная комментарием @Neil Kirk.
EDIT3: Еще одна проблема с методом vector
(по крайней мере в этом случае) заключается в том, что вы не можете перебирать элементы с помощью const T
или T
. Он допускает итерацию только с помощью const T&
(когда он static const
) и const T&
/T&
(когда он static
). Какова причина этого ограничения?
Описательный ответ на решения
@Yakk решение является единственным решением, а также работает на Visual С++ 2013 Update 4 RC.
Я нахожу это ошеломляющим, что такую тривиальную проблему сложно реализовать с использованием последнего стандарта С++ 11/14.
Ответы
Ответ 1
Данные не должны храниться в классе. Фактически, хранение данных в члене static
класса приводит к утечке деталей реализации.
Все, что вам нужно, - это доступность данных и глобальные данные для типа класса. Это не связано с раскрытием сведений о хранении: все, что вам нужно предоставить, - это сведения о доступе к хранилищу.
В частности, вы хотите выставить возможность цикла for(:)
по данным и работать с ним в стиле С++ 11. Так что выставляйте именно это.
Храните данные в анонимном пространстве имен в файле класса .cpp
в массиве C-стиля (или std::array
, мне все равно).
Выставить в классе следующее:
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()) {}
};
который является, по крайней мере, эскизом некоторых классов представления. живой пример
Затем выведите array_view<MyClass>
в качестве члена static
вашего класса, который инициализируется массивом, созданным в файле .cpp
.
range<It>
- это диапазон итераторов, который действует как контейнер, не являющийся владельцем. Выполняется некоторая глупость, чтобы блокировать вызовы, не связанные с постоянным временем, на size
или []
на уровне SFINAE. back()
отображается и просто не скомпилируется, если вы вызываете его на недействительных итераторах.
A make_range(Container)
делает range<It>
более полезным.
array_view<T>
является range<T*>
, который имеет кучу конструкторов из смежных буферных контейнеров, таких как C-массивы, std::array
и std::vector
s. (фактически исчерпывающий список).
Это полезно, потому что доступ через array_view
примерно так же эффективен, как доступ к исходному указателю-первому элементу массива, но мы получаем много хороших методов, которые есть в контейнерах, и он работает с диапазоном для петель. В общем случае, если функция принимает std::vector<T> const& v
, вы можете заменить ее на функцию, которая принимает array_view<T> v
, и она будет заменой. Большим исключением является operator vector
, который является явным, чтобы избежать случайных распределений.
Ответ 2
Мне лично нравится ваш constexpr static int my_array[] = {MyClass{1, 2, 3}, MyClass{1, 2, 3}};
. Я не думаю, что вам следует уклониться от этого, если массив C-стиля соответствует вашим потребностям.
Если вы действительно хотите использовать std::vector
, хотя вы можете использовать static const std::vector<MyClass*> vec_pre;
. Таким образом, ваш файл .cpp
будет иметь это вверху:
namespace{
MyClass A{1, 2, 3}, B{1, 2, 3}, C{1, 2, 3};
}
const std::vector<MyClass*> MyClass::vec_pre{&A, &B, &C};
ИЗМЕНИТЬ после DarkMatter комментариев:
После прочтения ваших комментариев, похоже, что может быть какая-то опасность для обслуживания моего метода. Он все равно может быть выполнен следующим образом: .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}};
Вы также можете удалить дублирование проблемы ремонтопригодности записи, создав макрос для этого.
Ответ 3
Вот способ установки вектора без копирования или перемещения.
Он не использует строчный инициализатор, но ваш вступительный абзац предполагает, что ваша главная задача - избегать копий и движений; а не абсолютное требование использовать фиксированный инициализатор.
// 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;
}