Tại sao chúng ta không thể thay đổi con trỏ dữ liệu của std :: vector?
Tôi có một mảng char* sourcevà một vectơ std::vector<char> target. Tôi muốn làm cho vectơ targettrỏ tới sourcetrong O (1) mà không cần sao chép dữ liệu.
Vài điều dọc theo những dòng này:
#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;
Tại sao không thể thay đổi thủ công nơi một vectơ trỏ tới? Có một số vấn đề cụ thể, không thể quản lý được sẽ phát sinh nếu điều này có thể xảy ra không?
Trả lời
vectorLớp C ++ hỗ trợ thêm và xóa các phần tử, với thứ tự liên tiếp được đảm bảo trong bộ nhớ. Nếu bạn có thể khởi tạo vectorbằng bộ đệm bộ nhớ hiện có và thêm đủ phần tử vào đó, nó sẽ bị tràn hoặc yêu cầu phân bổ lại.
Giao diện của vectorgiả định rằng nó quản lý bộ đệm bên trong của nó, nghĩa là nó có thể cấp phát, phân bổ, thay đổi kích thước bất cứ khi nào nó muốn (tất nhiên là trong spec). Nếu bạn cần một thứ gì đó không được phép quản lý bộ đệm của nó, bạn không thể sử dụng vector- hãy sử dụng một cấu trúc dữ liệu khác hoặc tự viết một cấu trúc dữ liệu.
Bạn có thể tạo một vectorđối tượng bằng cách sao chép dữ liệu của mình (sử dụng một hàm tạo có hai con trỏ hoặc assign), nhưng rõ ràng đây không phải là điều bạn muốn.
Ngoài ra, bạn có thể sử dụng string_view, trông gần như hoặc có thể chính xác những gì bạn cần.
std::vectorđược coi là chủ sở hữu của bộ đệm bên dưới. Bạn có thể thay đổi bộ đệm nhưng sự thay đổi này gây ra phân bổ tức là tạo một bản sao của bộ đệm nguồn mà bạn không muốn (như đã nêu trong câu hỏi).
Bạn có thể làm như sau:
#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;
}
nhưng một lần nữa std::vector::assign:
Thay thế nội dung bằng bản sao của những nội dung đó trong phạm vi [đầu tiên, cuối cùng).
Vì vậy, sao chép được thực hiện một lần nữa. Bạn không thể thoát khỏi nó trong khi sử dụng std::vector.
Nếu bạn không muốn sao chép dữ liệu, thì bạn nên sử dụng std::spantừ C ++ 20 (hoặc tạo khoảng thời gian của riêng bạn) hoặc sử dụng std::string_view(có vẻ phù hợp với bạn vì bạn có một mảng chars).
Tùy chọn thứ nhất: Sử dụng std::string_view
Vì bạn bị giới hạn ở C ++ 17, std::string_viewcó thể là lựa chọn hoàn hảo cho bạn. Nó xây dựng một khung nhìn của 3 ký tự đầu tiên của mảng ký tự bắt đầu bằng phần tử được trỏ bởi 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;
}
Tùy chọn thứ 2: Sử dụng std::spantừ C ++ 20
std::spanđến từ C ++ 20 nên nó có thể không phải là cách hoàn hảo nhất cho bạn, nhưng bạn có thể quan tâm đến nó là gì và nó hoạt động như thế nào. Bạn có thể std::spancoi đây là một phiên bản tổng quát hóa một chút std::string_viewbởi vì nó là một chuỗi các đối tượng liền kề thuộc bất kỳ loại nào, không chỉ các ký tự. Cách sử dụng tương tự như với 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;
}
Tùy chọn thứ 3: Khoảng của riêng bạn
Nếu bạn bị giới hạn ở C ++ 17, bạn có thể nghĩ đến việc tạo spancấu trúc của riêng mình . Nó vẫn có thể là một mức quá mức cần thiết nhưng hãy để tôi cho bạn thấy (btw hãy xem câu trả lời được chi tiết hơn này ):
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;
}
Vì vậy, cách sử dụng cũng giống như với phiên bản C ++ 20 của std::span.