ทำไมเราไม่สามารถเปลี่ยนตัวชี้ข้อมูลของ std :: vector ได้?

Dec 30 2020

ฉันมีอาร์เรย์และเวกเตอร์char* source std::vector<char> targetฉันต้องการให้เวกเตอร์targetชี้ไปที่sourceใน O (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

คลาส C ++ vectorรองรับการเพิ่มและลบองค์ประกอบพร้อมรับประกันลำดับต่อเนื่องในหน่วยความจำ หากคุณสามารถเริ่มต้นของคุณ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(ซึ่งดูเหมาะกับคุณเนื่องจากคุณมีอาร์เรย์char)

ตัวเลือกที่ 1: การใช้ std::string_view

เนื่องจากคุณ จำกัด ไว้ที่ C ++ 17 std::string_viewอาจเหมาะสำหรับคุณ มันสร้างมุมมองของ 3 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ได้ มันอาจจะยังเกินความสามารถ แต่ให้ฉันแสดงให้คุณเห็น (btw ลองดูคำตอบที่ละเอียดกว่านี้):

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.