Perché non possiamo cambiare il puntatore ai dati di std :: vector?

Dec 30 2020

Ho un array char* sourcee un vettore std::vector<char> target. Vorrei fare in modo che il vettore targetpunti sourcein O (1), senza copiare i dati.

Qualcosa in questo senso:

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

Perché non è possibile modificare manualmente il punto a cui punta un vettore? C'è qualche problema specifico e ingestibile che sorgerebbe se ciò fosse possibile?

Risposte

7 anatolyg Dec 30 2020 at 17:39

La vectorclasse C ++ supporta l'aggiunta e l'eliminazione di elementi, con un ordine consecutivo garantito in memoria. Se potessi inizializzare il tuo vectorcon il buffer di memoria esistente e aggiungere abbastanza elementi ad esso, andrebbe in overflow o richiederebbe la riallocazione.

L'interfaccia di vectorassume che gestisce il suo buffer interno, cioè può allocarlo, deallocarlo, ridimensionarlo ogni volta che vuole (all'interno delle specifiche, ovviamente). Se hai bisogno di qualcosa che non è autorizzato a gestire il suo buffer, non puoi usare vector: usa una struttura dati diversa o scrivine una tu stesso.

Puoi creare un vectoroggetto copiando i tuoi dati (usando un costruttore con due puntatori o assign), ma questo ovviamente non è quello che vuoi.

In alternativa, puoi usare string_view, che sembra quasi o forse esattamente quello di cui hai bisogno.

3 NutCracker Dec 30 2020 at 19:23

std::vectorè considerato il proprietario della riserva sottostante. È possibile modificare il buffer ma questa modifica provoca l'allocazione, ovvero la creazione di una copia del buffer di origine che non si desidera (come indicato nella domanda).

Potresti fare quanto segue:


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

ma ancora std::vector::assign:

Sostituisce il contenuto con copie di quelli nell'intervallo [primo, ultimo).

Quindi la copia viene eseguita di nuovo. Non puoi allontanarti da esso durante l'utilizzo std::vector.

Se non vuoi copiare i dati, dovresti usare std::spanda C ++ 20 (o creare il tuo span) o usare std::string_view(che sembra adatto a te dato che hai un array di chars).

1a opzione: utilizzo di std::string_view

Dato che sei limitato a C ++ 17, std::string_viewpotrebbe essere perfetto per te. Costruisce una vista dei primi 3 caratteri della matrice di caratteri a partire dall'elemento puntato da 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;
}

Seconda opzione: utilizzo std::spanda C ++ 20

std::spanproviene da C ++ 20 quindi potrebbe non essere il modo più perfetto per te, ma potresti essere interessato a cosa è e come funziona. Puoi pensare std::spana una versione un po 'generalizzata std::string_viewperché è una sequenza contigua di oggetti di qualsiasi tipo, non solo caratteri. L'utilizzo è simile a quello di 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;
}

3a opzione: il tuo intervallo

Se sei limitato a C ++ 17, puoi pensare di creare la tua spanstruttura. Potrebbe essere ancora eccessivo, ma lascia che te lo mostri (dai un'occhiata a questa risposta più elaborata ):

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

Quindi l'utilizzo è lo stesso della versione C ++ 20 di std::span.