Может ли std::array alias быть фрагментом большего массива?
Предположим, у нас есть указатель T* ptr;
а также ptr, ptr+1, … ptr+(n-1)
все относятся к действительным объектам типа T.
Можно ли получить к ним доступ, как если бы они были STL array
? Или делает следующий код:
std::array<T,n>* ay = (std::array<T,n>*) ptr
вызвать неопределенное поведение?
3 ответа
Да, это неопределенное поведение, классическое...
Во-первых, поймите, что вы только что сделали:
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
квалификация, но также бросил тип. Смотрите этот ответ: /questions/10404581/zachem-ispolzovat-staticcastintx-vmesto-intx/10404596#10404596... без разбора отбрасывать cv
квалификации также могут привести к UB.
Во-вторых, это неопределенное поведение для доступа к объекту через указатель, который был приведен из несвязанного типа. Смотрите строгое правило алиасинга (спасибо зениту). Поэтому любой доступ для чтения или записи через указатель ay
не определено Если вам чрезвычайно повезло, код должен мгновенно завершиться сбоем. Если это сработает, вас ждут злые дни....
Обратите внимание, что std::array
не является и никогда не будет таким же, как все, что не std::array
,
Просто чтобы добавить... В рабочем проекте стандарта C++ в нем перечислены явные правила преобразования. (вы можете прочитать их) и есть пункт о том, что
.....
5.4.3. Любое преобразование типов, не упомянутое ниже и не определенное пользователем явно ([class.conv]), является некорректным.
.....
Я предлагаю вам приготовить свой собственный array_view
(надеюсь, в C++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);
Да, это вызывает неопределенное поведение. Как правило, вы не можете приводить указатели на несвязанные типы между собой.
Код ничем не отличается от
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>
неопределенное поведение в том же масштабе, что и преобразование указателей в любые не связанные типы.
Я также не вижу преимущества использования уже созданного динамического массива как std::array
,
PS Обычный отказ от ответственности. В ролях, само по себе, всегда на 100% хорошо. Это косвенный указатель, который вызывает фейерверк, но эта часть для простоты опущена.
Я отвечаю на первый вопрос здесь, так как второй уже был рассмотрен в других ответах:
Резюме: вы...
иметь указатель
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;
}
Теперь вы можете использовать элементы массива, как если бы они были исходными указателями. И нет нигде неопределенного поведения.