Dlaczego nie możemy zmienić wskaźnika danych w std :: vector?
Mam tablicę char* source
i wektor std::vector<char> target
. Chciałbym, aby wektor target
wskazywał na source
O (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
vector
Klasa C ++ obsługuje dodawanie i usuwanie elementów z gwarantowaną kolejnością w pamięci. Gdybyś mógł zainicjować swój vector
istniejący bufor pamięci i dodać do niego wystarczającą liczbę elementów, przepełnienie lub wymagałoby ponownej alokacji.
Interfejs programu vector
zakł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ć vector
obiekt, 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.
std::vector
jest 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::span
z C ++ 20 (lub stworzyć własny zakres) lub użyć std::string_view
(co wygląda na odpowiednie dla ciebie, ponieważ masz tablicę char
s).
Pierwsza opcja: Korzystanie std::string_view
Ponieważ jesteś ograniczony do C ++ 17, std::string_view
moż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::span
z C ++ 20
std::span
pochodzi 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::span
jako nieco uogólnioną wersję, std::string_view
ponieważ 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 span
struktury. 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
.