Tại sao vector <bool> không phải là vùng chứa STL?


99

Mục 18 trong cuốn sách STL Hiệu quả của Scott Meyers : 50 Cách Cụ thể để Cải thiện Việc Sử dụng Thư viện Mẫu Chuẩn cho bạn nên tránh vector <bool>vì nó không phải là vật chứa STL và nó không thực sự giữ boolđược.

Đoạn mã sau:

vector <bool> v; 
bool *pb =&v[0];

sẽ không biên dịch, vi phạm yêu cầu của vùng chứa STL.

Lỗi:

cannot convert 'std::vector<bool>::reference* {aka std::_Bit_reference*}' to 'bool*' in initialization

vector<T>::operator []kiểu trả về được cho là có T&, nhưng tại sao nó lại là trường hợp đặc biệt cho vector<bool>?

Những gì vector<bool>thực sự bao gồm?

Mục còn nói:

deque<bool> v; // is a STL container and it really contains bools

Điều này có thể được sử dụng như một thay thế cho vector<bool>?

Bất cứ ai có thể xin vui lòng giải thích điều này?


22
Đó là một lỗi thiết kế trong C ++ 98, bây giờ được giữ lại để tương thích.
Oktalist

8
@ g-makulik, Không phải là việc sử dụng nó sẽ không biên dịch, chỉ là bạn không thể lưu trữ địa chỉ của một phần tử trong một con trỏ tới bool, vì phần tử không có địa chỉ riêng của nó.
chris

2
Có lẽ điều này sẽ hữu ích: stackoverflow.com/questions/670308/alternative-to-vectorbool
chris

1
@ g-makulik std::vector<bool> v;sẽ biên dịch. &v[0]sẽ không (lấy địa chỉ của một tạm thời).
Oktalist

4
vector<bool>có một đại diện xấu nhưng không hoàn toàn chính đáng như vậy: isocpp.org/blog/2012/11/on-vectorbool
TemplateRex

Câu trả lời:


114

Vì lý do tối ưu hóa không gian, tiêu chuẩn C ++ (trước đây là C ++ 98) gọi rõ ràng vector<bool>là một vùng chứa tiêu chuẩn đặc biệt, trong đó mỗi bool chỉ sử dụng một bit không gian thay vì một byte như bool thông thường (triển khai một loại "bitet động"). Để đổi lấy sự tối ưu hóa này, nó không cung cấp tất cả các khả năng và giao diện của một vùng chứa tiêu chuẩn thông thường.

Trong trường hợp này, vì bạn không thể lấy địa chỉ của một bit trong một byte, những thứ chẳng hạn như operator[]không thể trả về a bool&mà thay vào đó trả về một đối tượng proxy cho phép thao tác với bit cụ thể được đề cập. Vì đối tượng proxy này không phải là a bool&, bạn không thể gán địa chỉ của nó cho đối tượng bool*giống như bạn có thể với kết quả của một lệnh gọi toán tử như vậy trên vùng chứa "bình thường". Đổi lại, điều này có nghĩa bool *pb =&v[0];là mã đó không hợp lệ.

Mặt khác, dequekhông có bất kỳ chuyên môn hóa nào như vậy được gọi ra vì vậy mỗi bool chiếm một byte và bạn có thể lấy địa chỉ của giá trị trả về từ đó operator[].

Cuối cùng lưu ý rằng việc triển khai thư viện tiêu chuẩn MS (được cho là) ​​không tối ưu ở chỗ nó sử dụng kích thước đoạn nhỏ cho deques, có nghĩa là sử dụng deque thay thế không phải lúc nào cũng là câu trả lời đúng.


5
chúng ta có bất kỳ kiểu dữ liệu nào khác mà bất kỳ vùng chứa STL nào khác được chuyên dụng hoặc được gọi một cách rõ ràng không?
P0W

3
Điều này có áp dụng cho C ++ 11 std :: array <bool> không?
Sergio Basurco

4
@chuckleplant no, std::arraychỉ đơn thuần là một trình bao bọc tạo khuôn mẫu xung quanh một mảng thô T[n]với một số hàm trợ giúp như size(), sao chép / di chuyển ngữ nghĩa và các trình vòng lặp được thêm vào để làm cho nó tương thích với STL - và (rất may) nó không vi phạm các nguyên tắc riêng của nó (lưu ý của tôi sự hoài nghi của những :) 'chuyên' cho ' bool'.
underscore_d

Chỉ là một lựa chọn nit - kích thước (bool) không nhất thiết phải là một byte. stackoverflow.com/questions/4897844/…
Uri Raz

30

vector<bool>chứa các giá trị boolean ở dạng nén chỉ sử dụng một bit cho giá trị (chứ không phải 8 như cách mảng bool [] thực hiện). Không thể trả về một tham chiếu đến một bit trong c ++, vì vậy có một loại trợ giúp đặc biệt, "tham chiếu bit", cung cấp cho bạn giao diện với một số bit trong bộ nhớ và cho phép bạn sử dụng các toán tử và phôi chuẩn.


1
@PrashantSrivastava deque<bool>không chuyên biệt vì vậy nó thực sự chỉ là một bools giữ deque.
Konrad Rudolph

@PrashantSrivastava vector<bool>có một triển khai mẫu cụ thể. Tôi đoán, các thùng chứa STL khác, chẳng hạn như deque<bool>, không, vì vậy chúng chứa bool-s giống như bất kỳ loại nào khác.
Ivan Smirnov,

Đây là một câu hỏi hỏi một điều tương tự trong gỉ, nơi họ không cho phép các boolean bit đơn stackoverflow.com/questions/48875251/…
andy boot

25

Vấn đề là vector<bool>trả về một đối tượng tham chiếu proxy thay vì một tham chiếu thực, do đó mã kiểu C ++ 98 bool * p = &v[0];sẽ không biên dịch. Tuy nhiên, C ++ 11 hiện đại auto p = &v[0];có thể được thực hiện để biên dịch nếu operator&cũng trả về một đối tượng con trỏ proxy . Howard Hinnant đã viết một bài đăng trên blog mô tả chi tiết các cải tiến thuật toán khi sử dụng các tham chiếu và con trỏ proxy như vậy.

Scott Meyers có một Mục 30 dài trong C ++ Hiệu quả hơn về các lớp proxy. Bạn có thể đi một chặng đường dài để gần như bắt chước các kiểu nội trang: đối với bất kỳ kiểu nào đã cho T, một cặp proxy (ví dụ reference_proxy<T>iterator_proxy<T>) có thể được tạo ra đồng nhất với nhau theo nghĩa reference_proxy<T>::operator&()iterator_proxy<T>::operator*()là nghịch đảo của nhau.

Tuy nhiên, tại một số thời điểm, người ta cần ánh xạ lại các đối tượng proxy để hoạt động như T*hoặc T&. Đối với proxy của trình lặp, người ta có thể nạp chồng operator->()và truy cập vào Tgiao diện của mẫu mà không cần thực hiện lại tất cả các chức năng. Tuy nhiên, đối với proxy tham chiếu, bạn sẽ cần quá tải operator.()và điều đó không được phép trong C ++ hiện tại (mặc dù Sebastian Redl đã trình bày một đề xuất như vậy trên BoostCon 2013). Bạn có thể thực hiện một công việc chi tiết giống như một .get()thành viên bên trong proxy tham chiếu hoặc triển khai tất cả Tgiao diện của bên trong tham chiếu (đây là những gì được thực hiện chovector<bool>::bit_reference), nhưng điều này sẽ làm mất cú pháp nội trang hoặc giới thiệu chuyển đổi do người dùng xác định không có ngữ nghĩa nội trang cho chuyển đổi loại (bạn có thể có nhiều nhất một chuyển đổi do người dùng xác định cho mỗi đối số).

TL; DR : no vector<bool>không phải là một vùng chứa vì Tiêu chuẩn yêu cầu một tham chiếu thực, nhưng nó có thể được thực hiện để hoạt động gần giống như một vùng chứa, ít nhất là gần với C ++ 11 (auto) hơn nhiều so với C ++ 98.


10

Nhiều người coi vector<bool> chuyên môn hóa là một sai lầm.

Trong bài báo "Ngừng sử dụng các phần thư viện tiền nghiệm trong C ++ 17",
có một đề xuất để Xem xét lại chuyên môn hóa từng phần vectơ .

Đã có một lịch sử lâu dài về việc chuyên môn hóa từng phần bool của std :: vector không đáp ứng các yêu cầu của vùng chứa và đặc biệt, các trình vòng lặp của nó không đáp ứng các yêu cầu của trình vòng lặp truy cập ngẫu nhiên. Nỗ lực không dùng nữa vùng chứa này trước đây đã bị từ chối đối với C ++ 11, N2204 .


Một trong những lý do từ chối là không rõ ý nghĩa của việc từ chối một chuyên môn cụ thể của mẫu. Điều đó có thể được giải quyết bằng cách diễn đạt cẩn thận. Vấn đề lớn hơn là sự đặc biệt hóa (đóng gói) của vectơ mang lại một sự tối ưu hóa quan trọng mà các khách hàng của thư viện chuẩn thực sự tìm kiếm, nhưng sẽ không còn nữa. Không chắc rằng chúng tôi có thể không dùng phần này của tiêu chuẩn cho đến khi một phương tiện thay thế được đề xuất và chấp nhận, chẳng hạn như N2050 . Thật không may, không có đề xuất sửa đổi nào như vậy hiện đang được cung cấp cho Nhóm làm việc về Tiến hóa Thư viện.


5

Nhìn vào cách nó được thực hiện. STL xây dựng rất nhiều trên các mẫu và do đó các tiêu đề có chứa mã mà chúng thực hiện.

ví dụ: hãy xem việc triển khai stdc ++ ở đây .

cũng thú vị mặc dù không phải là một vectơ bit phù hợp với stl là llvm :: BitVector từ đây .

bản chất của llvm::BitVectorlà một lớp lồng nhau được gọi referencevà nạp chồng toán tử phù hợp để làm cho các BitVectorhành vi tương tự vectorvới một số hạn chế. Đoạn mã dưới đây là một giao diện được đơn giản hóa để cho thấy cách BitVector ẩn một lớp được gọi referenceđể làm cho việc triển khai thực gần như hoạt động giống như một mảng bool thực mà không sử dụng 1 byte cho mỗi giá trị.

class BitVector {
public:
  class reference {
    reference &operator=(reference t);
    reference& operator=(bool t);
    operator bool() const;
  };
  reference operator[](unsigned Idx);
  bool operator[](unsigned Idx) const;      
};

mã này ở đây có các thuộc tính tốt:

BitVector b(10, false); // size 10, default false
BitVector::reference &x = b[5]; // that's what really happens
bool y = b[5]; // implicitly converted to bool 
assert(b[5] == false); // converted to bool
assert(b[6] == b[7]); // bool operator==(const reference &, const reference &);
b[5] = true; // assignment on reference
assert(b[5] == true); // and actually it does work.

Mã này thực sự có một lỗ hổng, hãy thử chạy:

std::for_each(&b[5], &b[6], some_func); // address of reference not an iterator

sẽ không hoạt động vì assert( (&b[5] - &b[3]) == (5 - 3) );sẽ thất bại (trong llvm::BitVector)

đây là phiên bản llvm rất đơn giản. std::vector<bool>cũng có các trình vòng lặp hoạt động trong đó. như vậy cuộc gọi for(auto i = b.begin(), e = b.end(); i != e; ++i)sẽ hoạt động. và cả std::vector<bool>::const_iterator.

Tuy nhiên, vẫn có những hạn chế trong std::vector<bool>đó làm cho nó hoạt động khác nhau trong một số trường hợp.


3

Điều này đến từ http://www.cplusplus.com/reference/vector/vector-bool/

Vector bool Đây là phiên bản chuyên biệt của vector, được sử dụng cho các phần tử kiểu bool và tối ưu hóa cho không gian.

Nó hoạt động giống như phiên bản không chuyên biệt của vector, với những thay đổi sau:

  • Bộ nhớ không nhất thiết phải là một mảng các giá trị bool, nhưng việc triển khai thư viện có thể tối ưu hóa bộ nhớ để mỗi giá trị được
    lưu trữ trong một bit duy nhất.
  • Các phần tử không được tạo bằng đối tượng cấp phát, nhưng giá trị của chúng được đặt trực tiếp trên bit thích hợp trong bộ nhớ trong.
  • Chức năng thành viên lật và chữ ký mới để hoán đổi thành viên.
  • Một loại thành viên đặc biệt, tham chiếu, một lớp truy cập các bit riêng lẻ trong bộ nhớ trong của vùng chứa với giao diện
    mô phỏng tham chiếu bool. Ngược lại, kiểu thành viên const_reference là bool thuần túy.
  • Các kiểu con trỏ và trình vòng lặp được sử dụng bởi vùng chứa không nhất thiết không phải là con trỏ hoặc trình vòng lặp phù hợp, mặc dù chúng
    sẽ mô phỏng hầu hết các hành vi mong đợi của chúng.

Những thay đổi này cung cấp một giao diện kỳ ​​lạ cho chuyên ngành này và ưu tiên tối ưu hóa bộ nhớ hơn xử lý (có thể phù hợp với nhu cầu của bạn hoặc có thể không phù hợp với bạn). Trong mọi trường hợp, không thể khởi tạo trực tiếp mẫu vectơ không chuyên biệt cho bool. Các giải pháp để tránh phạm vi này là sử dụng một loại khác (char, unsigned char) hoặc vùng chứa (như deque) để sử dụng các loại trình bao bọc hoặc chuyên biệt hơn cho các loại trình cấp phát cụ thể.

bitset là một lớp cung cấp một chức năng tương tự cho các mảng bit có kích thước cố định.


1
Điều này không trả lời câu hỏi trực tiếp. Tốt nhất, nó yêu cầu người đọc suy ra những điều được giải thích trong phần tóm tắt chung này khiến nó không phải là STL.
underscore_d
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.