Можем ли мы использовать обычную арифметику указателей с std::array?

Я хочу разобраться, как использовать арифметику указателей в старом стиле для указателей на элементы класса std::array. Следующий код (возможно, неудивительно) не компилируется:

int main(int argc, char *argv[])
{
    double* data1 = new double[(int)std::pow(2,20)];
    std::cout << *data1 << " " << *(data1 +1) << std::endl;
    delete data1;
    data1 = NULL;

    double* data2 = new std::array<double, (int)std::pow(2,20)>;
    std::cout << *data2 << " " << *(data2 +1) << std::endl;
    delete data2;
    data2 = NULL;

    return 0;
}

В качестве упражнения я хочу использовать всю обычную арифметику с указателями, но вместо того, чтобы указывать на двойной массив старого стиля, я хочу, чтобы он указывал на элементы std::array. Мое мышление с этой строкой:

    double* data2 = new std::array<double, (int)std::pow(2,20)>;

должен указать компилятору, что data2 - указатель на первый элемент выделенной кучи std::array<double,(int)std::pow(2,20)>,

Меня учили, что double* name = new double[size]; означает ТОЧНО следующее: "Стек выделяет память для указателя на ОДИН двойник и присваивает имя указателю nameзатем куча выделяет массив двойных размеров sizeзатем установите указатель на первый элемент массива ". Поскольку приведенный выше код не компилируется, меня, должно быть, учили чему-то неправильному, поскольку тот же синтаксис не работает для массивов std::.

Это поднимает пару вопросов:

  1. Каков фактический смысл заявления type* name = new othertype[size];?
  2. Как я могу достичь того, что я хочу, используя std:: array?
  3. Наконец, как я могу добиться того же, используя std:: unqiue_ptr и std:: make_unique?

6 ответов

Решение

Меня учили, что double* name = new double[size]; означает ТОЧНО следующее: "Стек выделяет память для указателя на ОДИН двойник и присваивает имя указателю, затем куча выделяет массив двойных размеров, а затем устанавливает указатель так, чтобы он указывал на первый элемент массива". код не компилируется, меня, должно быть, учили чему-то неправильному, поскольку тот же синтаксис не работает для массивов std::.

Вы правы в этом утверждении, но имейте в виду, что способ, которым это работает, заключается в том, что new[] это другой оператор из new, Когда вы динамически выделяете std::arrayвы вызываете один объект newи возвращенный указатель указывает на std::array сам объект

Вы можете сделать арифметику указателя на содержимое std::array, Например, data2.data() + 1 это указатель на data2[1], Обратите внимание, что вы должны позвонить .data() чтобы получить указатель на базовый массив.

Во всяком случае, не динамически распределять std::array объекты. Избегайте динамического выделения, если это возможно, но если вам это нужно, используйте std::vector,

Можем ли мы использовать обычную арифметику указателей с std::array?

Да, конечно, вы можете - но не в самом массиве, который является объектом. Скорее, вы используете адрес данных в массиве, который вы получаете с std::array"s data() метод, вот так:

std::array<double, 2>  data2 { 12.3, 45.6 };
double* raw_data2 = data2.data(); // or &(*data2.begin());
std::cout << *raw_data2 << " " << *(raw_data2 + 1) << std::endl;

и это компилируется и работает нормально. Но вам, вероятно, на самом деле не нужно использовать арифметику указателей, и вы могли бы просто написать свой код по-другому, используя более приятную абстракцию std::array,

PS - Избегайте использования явного выделения памяти с new а также delete(см. пункт C++ Core Guidelines об этой проблеме). В вашем случае вам вообще не нужно выделять кучу - точно так же, как в обычном массиве.

Вы можете иметь доступ к представлению "необработанный указатель" std::array с использованием data() функция-член. Тем не менее, точка std::array это то, что вам не нужно делать это:

int main(int argc, char *argv[])
{
    std::array<double, 2> myArray;
    double* data = myArray.data();
    // Note that the builtin a[b] operator is exactly the same as
    // doing *(a+b).
    // But you can just use the overloaded operator[] of std::array.
    // All of these thus print the same thing:
    std::cout << *(data) << " " << *(data+1) << std::endl;
    std::cout << data[0] << " " << data[1] << std::endl;
    std::cout << myArray[0] << " " << myArray[1] << std::endl;

    return 0;
}

std::array это контейнер STL в конце концов!

auto storage = std::array<double, 1 << 20>{};
auto data = storage.begin();
std::cout << *data << " " << *(data + 1) << std::endl;

Значение обобщенного:

type* name = new othertype[size];

В конечном итоге "Мне нужна переменная name это указатель на type и инициализировать это с непрерывным распределением size случаи othertype с помощью new[]"Обратите внимание, что это включает в себя кастинг и может даже не работать как othertype а также type может не поддерживать эту операцию. std::array из double не эквивалентно указателю на double, Это указатель на std::array, точка, но если вы хотите притворяться, что это double и вы не возражаете, если ваша программа падает из-за неопределенного поведения, вы можете продолжить. Ваш компилятор должен предупредить вас здесь, и если это не так, ваши предупреждения недостаточно строги.

Контейнеры стандартной библиотеки - все об итераторах, а не указателях, и особенно не арифметике указателей. Итераторы гораздо более гибки и способны, чем указатели, они могут обрабатывать экзотические структуры данных, такие как связанные списки, деревья и т. Д., Не налагая больших затрат на вызывающего.

Некоторые контейнеры, такие как std::vector а также std::array поддержка "итераторов произвольного доступа", которые являются формой прямого указательного доступа к их содержимому: a[1] и так далее. Внимательно прочитайте документацию по каждому данному контейнеру, так как некоторые позволяют это, а многие нет.

Помните, что "переменная" и "размещены в стеке" не обязательно одно и то же. Оптимизирующий компилятор может и будет размещать этот указатель там, где он хочет, включая регистры вместо памяти, или вообще нигде, если он думает, что может обойтись без него, не нарушая выраженное поведение вашего кода.

Если ты хочешь std::array, что вы действительно делаете как контейнеры стандартной библиотеки почти всегда лучше, чем массивы в стиле C:

std::array<double, 2> data2;

Если вам нужно поделиться этой структурой, вам нужно будет рассмотреть вопрос об использовании std::unique_ptr стоит того. Объем памяти этой вещи будет крошечным, и ее копирование будет тривиальным, поэтому задействовать относительно дорогую функцию управления памятью бессмысленно.

Если вы передвигаетесь по более крупной структуре, рассмотрите возможность использования ссылки вместо этого и найдите структуру в самой центральной структуре данных, которая у вас есть, чтобы ее не нужно было копировать по проекту.

Конечно, это все законно:

template<class T, std::size_t N>
T* alloc_array_as_ptr() {
  auto* arr = new std::array<T,N>;
  if (!arr) return nullptr;
  return arr->data();
}
template<class T, std::size_t N>
T* placement_array_as_ptr( void* ptr ) {
  auto* arr = ::new(ptr) std::array<T,N>;
  return arr->data();
}
template<std::size_t N, class T>
std::array<T, N>* ptr_as_array( T* in ) {
  if (!in) return nullptr;
  return reinterpret_cast<std::array<T,N>*>(in); // legal if created with either above 2 functions!
}
// does not delete!
template<std::size_t N, class T>
void destroy_array_as_ptr( T* t ) {
  if (!t) return;
  ptr_as_array<N>(t)->~std::array<T,N>();
}
// deletes
template<std::size_t N, class T>
void delete_array_as_ptr(T* t) {
  delete ptr_as_array<N>(t);
}

вышесказанное, шокирующе, на самом деле законно, если используется идеально Указатель на первый элемент массива является указателем, взаимно конвертируемым со всем массивом std::.

Вы должны сами следить за размером массива.

Я бы не советовал делать это.

double* data2 = new std::array<double, (int)std::pow(2,20)>;

Этот ^^^ не должен компилироваться по двум причинам.

Во-первых, std::array шаблон, где вторым параметром является массив SIZE. Размер должен быть известен во время компиляции, что не так в вашем примере. (int)std::pow(2,20) значение будет известно только во время выполнения. Есть шаблоны приемов создания constexpr но это не главное.

Во-вторых, даже если размер известен, new возвращает указатель на объект, который в вашем случае std::array<double, SIZE> и не double, Вы можете получить доступ к необработанному массиву std::array с .data() как уже упоминалось другими, но все это кажется ненужным сложным.

Наконец, как я могу добиться того же, используя std::unqiue_ptr и std::make_unique?

Вы можете получить необработанный указатель с .get() метод, но было бы лучше использовать operator[] для доступа к отдельным элементам:

auto data2 = std::make_unique<double[]>((int)std::pow(2, 20));
std::cout << data2[0] << " " << data2[1] << std::endl;
Другие вопросы по тегам