Может ли std:: array использовать фрагмент большего массива?
Предположим, что у нас есть указатель T* ptr;
и ptr, ptr+1, … ptr+(n-1)
все относятся к действительным объектам типа T.
Можно ли получить к ним доступ, как если бы они были STL array
? Или имеет следующий код:
std::array<T,n>* ay = (std::array<T,n>*) ptr
вызывать поведение undefined?
Ответы
Ответ 1
Да, его поведение Undefined, классическое...
Во-первых, поймите, что вы только что сделали:
std::array<T,n>* ay = (std::array<T,n>*) ptr
можно перевести как:
using Arr = std::array<T,n>;
std::array<T,n>* ay = reinterpret_cast<Arr*>( const_cast<TypeOfPtr>(ptr));
Вы не просто отбросили все, const
и volatile
квалификацию, но также набросили тип. См. Этот ответ: fooobar.com/questions/2175/...... бездисковое отбрасывание квалификаций cv
также может привести к UB.
Во-вторых, поведение Undefined для доступа к объекту через указатель, который был отличен из несвязанного типа. См. строгое правило псевдонимов (Спасибо зениту). Поэтому любой доступ к чтению или записи через указатель ay
равен undefined. Если вам очень повезло, код должен немедленно сработать. Если это сработает, злые дни ждут вас....
Обратите внимание, что std::array
не является и никогда не будет таким же, как и все, что не является std::array
.
Просто добавьте... В рабочий черновик стандарта С++ он перечисляет из явных правил преобразования. (вы можете прочитать их) и имеет пункт, в котором говорится, что
.....
5.4.3: Любое преобразование типа, не упомянутое ниже, и явно не определяемое пользователь ([class.conv]) плохо сформирован.
.....
Я предлагаю вам приготовить собственный array_view
(надеюсь, он появится на С++ 17). Это очень легко. Или, если вы хотите получить какую-то собственность, вы можете приготовить простой такой:
template<typename T>
class OwnedArray{
T* data_ = nullptr;
std::size_t sz = 0;
OwnedArray(T* ptr, std::size_t len) : data_(ptr), sz(len) {}
public:
static OwnedArray own_from(T* ptr, std::size_t len)
{ return OwnedArray(ptr, len); }
OwnedArray(){}
OwnedArray(OwnedArray&& o)
{ data_ = o.data_; sz = o.sz; o.data_=nullptr; o.sz=0; }
OwnedArray& operator = (OwnedArray&& o)
{ delete[] data_; data_ = o.data_; sz = o.sz; o.data_=nullptr; o.sz=0; }
OwnedArray(const OwnedArray& o) = delete;
OwnedArray& operator = (const OwnedArray& o) = delete;
~OwnedArray(){ delete[] data_; }
std::size_t size() const { return sz; }
T* data() return { data_; }
T& operator[] (std::size_t idx) { return data_[idx]; }
};
... и вы можете развернуть больше функций-членов/const-квалификаций по своему усмотрению. Но это имеет оговорки... Указателю должно быть выделено сквозное new T[len]
Таким образом, вы можете использовать его в своем примере следующим образом:
auto ay = OwnedArray<decltype(*ptr)>::own_from(ptr, ptr_len);
Ответ 2
Да, это вызывает поведение undefined. Как правило, вы не можете указывать указатели на несвязанные типы между собой.
Код ничем не отличается от
std::string str;
std::array<double,10>* arr = (std::array<double,10>*)(&str);
Объяснение: Стандарт не обеспечивает никаких гарантий совместимости между std::array<T,n>
и T*
. Его просто нет. Он не говорит, что std::array
является тривиальным типом. При отсутствии таких гарантий любое преобразование между T*
и std::array<T,n>
- это поведение undefined в том же масштабе, что и преобразование указателей в любые несвязанные типы.
Я также не понимаю, в чем преимущество доступа к уже построенному динамическому массиву как std::array
.
P.S. Обычная оговорка. Cast, на его собственном, всегда на 100% отлично. Это косвенность приведенного указателя, который запускает фейерверк, но эта часть не указана для упрощения.
Ответ 3
Я отвечаю на первый вопрос здесь, поскольку второй уже рассмотрен в других ответах:
Recap: вы...
имеют указатель T* ptr;
и ptr, ptr+1, … ptr+(n-1)
все относятся к действительным объектам типа T.
И вы спрашиваете, есть ли это...
можно получить доступ к ним, как если бы они были STL array
?
Ответ: Это не проблема, но она работает по-разному, как вы оценили в своем примере кода:
std::array<T*, N> arr;
for(int i = 0; i<N; ++i)
{
arr[i] = ptr + i;
}
Теперь вы можете использовать элементы массива, как если бы они были оригинальными указателями. И нет никакого поведения undefined где-либо.
Ответ 4
Это вызывает поведение undefined. Вы используете reinterpret_cast
для переноса между несвязанными типами, а правила на них довольно строгие. Для ближайшего предложения, предлагающего определенное поведение, рассмотрите следующий союз:
union {
int carray[10];
std::array<10, int> sarray;
}
Если два члена союза имеют один и тот же префикс (т.е. их первые члены k являются одним и тем же типом), вы можете написать объединению через один член и прочитать от другого, если вы ограничиваете себя членами в общий префикс. Если вы с уверенностью знаете, что std::array
имеет массив C в качестве своего первого члена, то это определено. Кажется, что это "в основном" требуется (Является ли размер std:: array определенным стандартом).
Итак, в этом случае, если вы берете адрес carray
, у вас есть int*
, который можно условно отнести к std::array<10, int>
косвенно через объединение.
Однако на самом деле нет причин для этого. Скорее всего, что-то вроде array_view
. Это не в стандарте, но основная идея состоит в том, что его общий способ обернуть представление (не владение) любого массива. Базовая версия довольно просто реализовать самостоятельно или вы можете проверить это: https://github.com/rhysd/array_view.