Почему мы не можем изменить указатель данных std :: vector?

Dec 30 2020

У меня есть массив char* sourceи вектор std::vector<char> target. Я хотел бы, чтобы вектор targetуказывал на sourceO (1) без копирования данных.

Что-то в этом роде:

#include <vector>

char* source = new char[3] { 1, 2, 3 };
std::vector<char> target;
target.resize(3);

target.setData(source); // <- Doesn't exist
// OR
std::swap(target.data(), source); // <- swap() does not support char*
delete[] source;

Почему невозможно вручную изменить направление вектора? Возникла бы какая-то конкретная неуправляемая проблема, если бы это было возможно?

Ответы

7 anatolyg Dec 30 2020 at 17:39

vectorКласс C ++ поддерживает добавление и удаление элементов с гарантированным последовательным порядком в памяти. Если бы вы могли инициализировать свой vectorсуществующий буфер памяти и добавить в него достаточное количество элементов, он либо переполнился бы, либо потребовал перераспределения.

Интерфейс vectorпредполагает, что он управляет своим внутренним буфером, то есть может выделять, освобождать, изменять его размер в любое время (в рамках спецификации, конечно). Если вам нужно что-то, что не может управлять своим буфером, вы не можете его использовать vector- используйте другую структуру данных или напишите ее самостоятельно.

Вы можете создать vectorобъект, скопировав свои данные (используя конструктор с двумя указателями или assign), но это явно не то, что вам нужно.

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

3 NutCracker Dec 30 2020 at 19:23

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

Вы можете сделать следующее:


#include <vector>

int main() {
    char* source = new char[3] { 1, 2, 3 };
    std::vector<char> target;
    target.resize(3);
    target.assign(source, source + 3);
    delete[] source;
    return 0;
}

но опять же std::vector::assign:

Заменяет содержимое копиями содержимого диапазона [первый, последний).

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

Если вы не хотите копировать данные, вам следует использовать std::spanиз C ++ 20 (или создать свой собственный диапазон) или использовать std::string_view(что выглядит подходящим для вас, поскольку у вас есть массив chars).

1-й вариант: Использование std::string_view

Поскольку вы ограничены C ++ 17, это std::string_viewможет быть идеально для вас. Он создает представление первых трех символов массива символов, начиная с элемента, на который указывает source.

#include <iostream>
#include <string_view>

int main() {
    char* source = new char[3] { 1, 2, 3 };

    std::string_view strv( source, 3 );

    delete[] source;

    return 0;
}

2-й вариант: использование std::spanиз C ++ 20

std::spanисходит из C ++ 20, так что это может быть не самый лучший способ для вас, но вам может быть интересно, что это такое и как работает. Вы можете рассматривать его std::spanкак немного обобщенную версию, std::string_viewпотому что это непрерывная последовательность объектов любого типа, а не только символов. Использование аналогично std::string_view:

#include <span>
#include <iostream>

int main() {
    char* source = new char[3] { 1, 2, 3 };

    std::span s( source, 3 );

    delete[] source;

    return 0;
}

3-й вариант: ваш собственный промежуток времени

Если вы ограничены C ++ 17, вы можете подумать о создании своей собственной spanструктуры. Это все еще может быть излишним, но позвольте мне показать вам (кстати, взгляните на этот более подробный ответ ):

template<typename T>
class span {
   T* ptr_;
   std::size_t len_;

public:
    span(T* ptr, std::size_t len) noexcept
        : ptr_{ptr}, len_{len}
    {}

    T& operator[](int i) noexcept {
        return *ptr_[i];
    }

    T const& operator[](int i) const noexcept {
        return *ptr_[i];
    }

    std::size_t size() const noexcept {
        return len_;
    }

    T* begin() noexcept {
        return ptr_;
    }

    T* end() noexcept {
        return ptr_ + len_;
    }
};

int main() {
    char* source = new char[3] { 1, 2, 3 };

    span s( source, 3 );

    delete[] source;

    return 0;
}

Таким образом, использование такое же, как и в версии C ++ 20 std::span.