Mengapa kita tidak bisa mengubah penunjuk data dari std :: vector?

Dec 30 2020

Saya memiliki array char* sourcedan vektor std::vector<char> target. Saya ingin membuat targettitik vektor ke sourcedalam O (1), tanpa menyalin data.

Sesuatu seperti ini:

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

Mengapa tidak mungkin untuk mengubah secara manual di mana vektor menunjuk? Adakah masalah khusus yang tidak dapat ditangani yang akan muncul jika hal ini memungkinkan?

Jawaban

7 anatolyg Dec 30 2020 at 17:39

vectorKelas C ++ mendukung penambahan dan penghapusan elemen, dengan urutan yang dijamin dalam memori. Jika Anda dapat menginisialisasi Anda vectordengan buffer memori yang ada, dan menambahkan elemen yang cukup ke dalamnya, itu akan meluap atau memerlukan realokasi.

Antarmuka vectormengasumsikan bahwa ia mengelola buffer internalnya, yaitu, ia dapat mengalokasikan, membatalkan alokasi, mengubah ukurannya kapan pun ia mau (dalam spesifikasi, tentu saja). Jika Anda memerlukan sesuatu yang tidak diizinkan untuk mengelola buffernya, Anda tidak dapat menggunakan vector- gunakan struktur data yang berbeda atau tulis sendiri.

Anda dapat membuat vectorobjek dengan menyalin data Anda (menggunakan konstruktor dengan dua penunjuk atau assign), tetapi ini jelas bukan yang Anda inginkan.

Atau, Anda dapat menggunakan string_view, yang terlihat hampir atau mungkin persis seperti yang Anda butuhkan.

3 NutCracker Dec 30 2020 at 19:23

std::vectordianggap sebagai pemilik buffer yang mendasarinya. Anda dapat mengubah buffer tetapi perubahan ini menyebabkan alokasi, yaitu membuat salinan buffer sumber yang tidak Anda inginkan (seperti yang dinyatakan dalam pertanyaan).

Anda dapat melakukan hal berikut:


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

tapi sekali lagi std::vector::assign:

Mengganti konten dengan salinan yang ada dalam rentang [pertama, terakhir).

Jadi penyalinan dilakukan lagi. Anda tidak bisa lepas darinya saat menggunakan std::vector.

Jika Anda tidak ingin menyalin data, maka Anda harus menggunakan std::spandari C ++ 20 (atau buat span Anda sendiri) atau gunakan std::string_view(yang terlihat cocok untuk Anda karena Anda memiliki array chars).

Opsi pertama: Menggunakan std::string_view

Karena Anda terbatas pada C ++ 17, std::string_viewmungkin cocok untuk Anda. Ini membangun tampilan dari 3 karakter pertama dari larik karakter yang dimulai dengan elemen yang ditunjuk oleh 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;
}

Opsi kedua: Menggunakan std::spandari C ++ 20

std::spanberasal dari C ++ 20 jadi ini mungkin bukan cara yang paling sempurna untuk Anda, tetapi Anda mungkin tertarik dengan apa itu dan bagaimana cara kerjanya. Anda dapat menganggapnya std::spansebagai versi yang sedikit digeneralisasi std::string_viewkarena ini adalah urutan objek yang berdekatan dari jenis apa pun, bukan hanya karakter. Penggunaannya mirip dengan 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;
}

Opsi ketiga: Rentang Anda sendiri

Jika Anda terbatas pada C ++ 17, Anda dapat memikirkan untuk membuat spanstruct Anda sendiri . Ini mungkin masih berlebihan tapi izinkan saya menunjukkan kepada Anda (btw lihat jawaban yang lebih terperinci ini ):

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

Jadi penggunaannya sama dengan versi C ++ 20 std::span.