Tại sao vectơ của libc ++ <bool> :: const_reference không phải là bool?


92

Phần 23.3.7 Lớp vector<bool>[vector.bool], đoạn 1 nêu rõ:

template <class Allocator> class vector<bool, Allocator> {
public:
    // types:
    typedef bool              const_reference;
    ...

Tuy nhiên, chương trình này không thể biên dịch khi sử dụng libc ++:

#include <vector>
#include <type_traits>

int
main()
{
    static_assert(std::is_same<std::vector<bool>::const_reference, bool>{}, "?");
}

Hơn nữa, tôi lưu ý rằng tiêu chuẩn C ++ đã nhất quán trong đặc điểm kỹ thuật này từ thời C ++ 98. Và tôi lưu ý thêm rằng libc ++ đã luôn không tuân theo đặc điểm kỹ thuật này kể từ lần đầu tiên ra mắt libc ++.

Động cơ cho sự không tuân thủ này là gì?

Câu trả lời:


99

Động cơ cho phần mở rộng này, có thể phát hiện được bởi một chương trình phù hợp, và do đó không phù hợp, là để làm cho vector<bool>hành vi giống hơn vector<char>đối với các tham chiếu (const và cách khác).

Giới thiệu

Kể từ năm 1998, vector<bool>đã được chế giễu là "không phải là một container." LWG 96 , một trong những vấn đề đầu tiên của LWG, đã khởi động cuộc tranh luận. Ngày nay, 17 năm sau, vector<bool>phần lớn vẫn không thay đổi.

Bài báo này đi vào một số ví dụ cụ thể về cách hoạt động của vector<bool>khác với mọi cách diễn đạt khác vector, do đó làm tổn hại đến mã chung. Tuy nhiên, cùng một bài báo thảo luận về độ dài các đặc tính hiệu suất rất tốt vector<bool>có thể có nếu được triển khai đúng cách.

Tóm lại : vector<bool>không phải là một thùng chứa tồi. Nó thực sự khá hữu ích. Nó chỉ có một cái tên xấu.

Quay lại const_reference

Như đã giới thiệu ở trên và chi tiết ở đây , điều tồi tệ vector<bool>là nó hoạt động trong mã chung chung khác với các bản vectorthuyết minh khác . Đây là một ví dụ cụ thể:

#include <cassert>
#include <vector>

template <class T>
void
test(std::vector<T>& v)
{
    using const_ref = typename std::vector<T>::const_reference;
    const std::vector<T>& cv = v;
    const_ref cr = cv[0];
    assert(cr == cv[0]);
    v[0] = 1;
    assert(true == cv[0]);
    assert(cr == cv[0]);  // Fires!
}

int
main()
{
    std::vector<char> vc(1);
    test(vc);
    std::vector<bool> vb(1);
    test(vb);
}

Thông số kỹ thuật tiêu chuẩn nói rằng xác nhận được đánh dấu // Fires!sẽ kích hoạt, nhưng chỉ khi testđược chạy với a vector<bool>. Khi chạy với một vector<char>(hoặc bất kỳ vectorbên cạnh boolkhi Tgán giá trị không mặc định thích hợp ), bài kiểm tra sẽ vượt qua.

Việc triển khai libc ++ đã tìm cách giảm thiểu các tác động tiêu cực của việc vector<bool>cư xử khác nhau trong mã chung. Một điều nó đã làm để đạt được điều này là tạo vector<T>::const_referencemột tham chiếu proxy , giống như tham chiếu đã chỉ định vector<T>::reference, ngoại trừ việc bạn không thể chỉ định thông qua nó. Đó là, trên libc ++, vector<T>::const_referencevề cơ bản là một con trỏ đến bit bên trong vector, thay vì bản sao của bit đó.

Trên libc ++, ở trên testchuyển cho cả vector<char>vector<bool>.

Chi phí gì?

Nhược điểm là phần mở rộng này có thể phát hiện được, như trong câu hỏi. Tuy nhiên, rất ít chương trình thực sự quan tâm đến loại chính xác của bí danh này và nhiều chương trình hơn quan tâm đến hành vi.

Động cơ cho sự không tuân thủ này là gì?

Để cung cấp cho ứng dụng khách libc ++ hành vi tốt hơn trong mã chung và có lẽ sau khi kiểm tra thực địa đầy đủ, hãy đề xuất phần mở rộng này cho một tiêu chuẩn C ++ trong tương lai để cải thiện toàn bộ ngành C ++.

Một đề xuất như vậy có thể ở dạng một vùng chứa mới (ví dụ bit_vector) có nhiều API giống như ngày nay vector<bool>, nhưng với một vài nâng cấp như được const_referencethảo luận ở đây. Tiếp theo là việc ngừng sử dụng (và cuối cùng là xóa bỏ) vector<bool>chuyên môn. bitsetcũng có thể sử dụng một chút nâng cấp trong bộ phận này, ví dụ như thêm const_referencevà một tập hợp các trình vòng lặp.

Đó là, trong nhận thức muộn màng bitsetvector<bool>(mà nên được đổi tên thành bit_vector- hoặc bất cứ điều gì), như arrayvector. Và sự tương tự phải giữ đúng hay không chúng ta đang nói về boolvalue_typecủa vectorarray.

Có nhiều ví dụ về các tính năng C ++ 11 và C ++ 14 bắt đầu dưới dạng phần mở rộng trong libc ++. Đây là cách các tiêu chuẩn phát triển. Thực tế chứng minh kinh nghiệm thực địa tích cực có ảnh hưởng mạnh mẽ. Các tiêu chuẩn dân gian là một nhóm bảo thủ khi nói đến việc thay đổi các thông số kỹ thuật hiện có (như chúng phải như vậy). Đoán, ngay cả khi bạn chắc chắn mình đoán đúng, là một chiến lược mạo hiểm để phát triển một tiêu chuẩn được quốc tế công nhận.


1
Câu hỏi: có thể / sẽ đề xuất dự thảo gần đây về trình vòng lặp proxy của @EricNiebler bằng cách nào đó hợp pháp hóa các tiện ích mở rộng libc ++ và đặt vector<bool>trên cơ sở hạng nhất hơn không?
TemplateRex

Ghi chú: Tôi muốn có một vector_bool<Alloc>và một array_bool<N>để đại diện cho các phiên bản được đóng gói (bao gồm các trình vòng lặp proxy truy cập ngẫu nhiên lặp lại tất cả các bit) của vector<bool, Alloc>array<bool, N>. Tuy nhiên, bitset<N>(và anh em họ của nó boost::dynamic_bitset<Alloc>) đại diện cho một sự trừu tượng khác: cụ thể là các phiên bản đóng gói của std::set<int>. Vì vậy, tôi muốn có, nói, bit_array<N>bit_vector<Alloc>trở thành người kế thừa nhượng quyền tập hợp bit, với các trình vòng lặp hai chiều thích hợp (lặp trên các bit 1, thay vì trên tất cả các bit). Suy nghĩ của bạn về điều này là gì?
TemplateRex

5
Đề xuất dự thảo của tôi về trình vòng lặp proxy sẽ tạo ra vector<bool>một vùng chứa truy cập ngẫu nhiên phù hợp với tiêu chuẩn. Nó không phải vector<bool>là một ý tưởng hay. :-) Tôi đồng ý với Howard. Nó nên được gọi là một cái gì đó khác.
Eric Niebler

1
Tại sao không có cách nào để chọn không tham gia các tiện ích mở rộng libc ++ và có được hành vi tuân thủ nghiêm ngặt? (Tôi thậm chí không yêu cầu đặt sự tuân thủ làm mặc định, chỉ là một cách để vô hiệu hóa các phần mở rộng của libc ++ để có thể viết mã di động). Như bạn đã biết, trước đây tôi đã bị cắn bởi các phần mở rộng libc ++ tuple và gần đây đã bị cắn bởi phần mở rộng bitset :: const_reference.
gnzlbg

5
@gnzlbg: Một lượng tài nguyên kinh tế và thời gian hữu hạn có sẵn cho sự phát triển ban đầu của libc ++. Sau đó, việc triển khai đã không thể làm cho mọi người dùng hài lòng. Với các nguồn lực sẵn có, sự cân bằng kỹ thuật đã được thực hiện nhằm cố gắng tối đa hóa số lượng người dùng hài lòng, tối đa hóa lợi ích cho cộng đồng C ++ nói chung. Xin lỗi về kinh nghiệm của bạn. Tôi lưu ý rằng các phần mở rộng tuple mà bạn đã sử dụng hiện đang nằm trong giấy làm việc C ++ 1z hiện tại. Về vấn đề đó, bạn đã vô tình hy sinh để nhiều người được lợi, và những người đó nợ bạn một món nợ ân tình.
Howard Hinnant
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.