Dlaczego nie możemy zmienić wskaźnika danych w std :: vector?

Dec 30 2020

Mam tablicę char* sourcei wektor std::vector<char> target. Chciałbym, aby wektor targetwskazywał na sourceO (1), bez kopiowania danych.

Coś w tym stylu:

#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;

Dlaczego nie można ręcznie zmienić miejsca, w którym wskazuje wektor? Czy jest jakiś konkretny, niemożliwy do rozwiązania problem, który pojawiłby się, gdyby było to możliwe?

Odpowiedzi

7 anatolyg Dec 30 2020 at 17:39

vectorKlasa C ++ obsługuje dodawanie i usuwanie elementów z gwarantowaną kolejnością w pamięci. Gdybyś mógł zainicjować swój vectoristniejący bufor pamięci i dodać do niego wystarczającą liczbę elementów, przepełnienie lub wymagałoby ponownej alokacji.

Interfejs programu vectorzakłada, że zarządza swoim wewnętrznym buforem, to znaczy może go przydzielać, cofać przydział, zmieniać jego rozmiar w dowolnym momencie (oczywiście w ramach specyfikacji). Jeśli potrzebujesz czegoś, co nie może zarządzać swoim buforem, nie możesz tego użyć vector- użyj innej struktury danych lub napisz własną.

Możesz utworzyć vectorobiekt, kopiując swoje dane (używając konstruktora z dwoma wskaźnikami lub assign), ale oczywiście nie jest to to, czego chcesz.

Alternatywnie możesz użyć string_view, który wygląda prawie lub dokładnie tak, jak potrzebujesz.

3 NutCracker Dec 30 2020 at 19:23

std::vectorjest uważana za właściciela bazowego bufora. Możesz zmienić bufor, ale ta zmiana powoduje alokację, czyli wykonanie kopii bufora źródłowego, którego nie chcesz (jak stwierdzono w pytaniu).

Możesz wykonać następujące czynności:


#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;
}

ale znowu std::vector::assign:

Zastępuje zawartość kopiami z zakresu [pierwszy, ostatni].

Więc kopiowanie jest wykonywane ponownie. Nie możesz od tego uciec podczas używania std::vector.

Jeśli nie chcesz kopiować danych, powinieneś użyć std::spanz C ++ 20 (lub stworzyć własny zakres) lub użyć std::string_view(co wygląda na odpowiednie dla ciebie, ponieważ masz tablicę chars).

Pierwsza opcja: Korzystanie std::string_view

Ponieważ jesteś ograniczony do C ++ 17, std::string_viewmoże być dla Ciebie idealny. Tworzy widok pierwszych 3 znaków tablicy znaków, zaczynając od elementu wskazywanego przez 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;
}

Druga opcja: używanie std::spanz C ++ 20

std::spanpochodzi z C ++ 20, więc może nie być dla Ciebie najdoskonalszym sposobem, ale możesz być zainteresowany tym, co to jest i jak działa. Można go traktować std::spanjako nieco uogólnioną wersję, std::string_viewponieważ jest to ciągła sekwencja obiektów dowolnego typu, a nie tylko znaków. Użycie jest podobne jak w przypadku 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;
}

Trzecia opcja: Twój własny zakres

Jeśli jesteś ograniczony do C ++ 17, możesz pomyśleć o stworzeniu własnej spanstruktury. To nadal może być przesada, ale pozwól, że ci pokażę (przy okazji spójrz na tę bardziej złożoną odpowiedź ):

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;
}

Więc użycie jest takie samo, jak w wersji C ++ 20 std::span.