2013-02-11 24 views
7

Với một lớp container, chẳng hạn như std::vector, có hai khái niệm khác nhau của liên tục-Ness: của container (nghĩa là kích thước của nó) và của các phần tử. Dường như std::vector lẫn lộn hai, như vậy mà mã đơn giản sau đây sẽ không biên dịch:const và không const của container và nội dung của nó

struct A { 
    A(size_t n) : X(n) {} 
    int&x(int i) const { return X[i]; } // error: X[i] is non-const. 
private: 
    std::vector<int> X; 
}; 

Lưu ý rằng mặc dù thành viên dữ liệu (ba con trỏ vào bắt đầu & cuối của dữ liệu và cuối cùng của bộ đệm được cấp phát) của std::vectorkhông phải được thay đổi bằng một cuộc gọi đến số operator[], thành viên này không phải là const - đây có phải là thiết kế lạ không?

Cũng lưu ý rằng cho con trỏ nguyên, hai khái niệm này của hằng số-Ness được gọn gàng tách ra, như vậy mà tương ứng với mã thô-con trỏ

struct B { 
    B(size_t n) : X(new int[n]) {} 
    ~B() { delete[] X; } 
    void resize(size_t n);     // non-const 
    int&x(int i) const { return X[i]; } // fine 
private: 
    int*X; 
}; 

công trình tốt.

Vì vậy, cách chính xác/được đề nghị để giải quyết vấn đề này khi sử dụng std::vector (không sử dụng mutable) là gì?

Là một const_cast<> như trong

int&A::x(int i) const { return const_cast<std::vector<int>&>(X)[i]; } 

coi là chấp nhận được (X được biết đến là phi const, vì vậy không UB đây)?

EDIT chỉ để tránh nhầm lẫn hơn nữa: Tôi muốn thay đổi các yếu tố, ví dụ: các nội dung của container nhưng không chứa chính nó (kích thước và/hoặc vị trí nhớ).

+0

Dữ liệu của 'std :: vector' có thể được thay đổi bởi lệnh gọi của bạn thành' toán tử [] '. Như bạn đã viết 'A :: X',' a.x (1) ++; 'là hoàn toàn hợp pháp, và sửa đổi nội dung của vectơ. –

+1

@DavidSchwartz * Nội dung * của một véc tơ không phải là dữ liệu * thực tế của nó * (mặc dù bạn có thể kết hợp chúng một cách hợp lý như vậy). Nếu bạn kiểm tra 'std :: vector', nó chỉ có 3 con trỏ làm dữ liệu (bắt đầu và kết thúc dữ liệu và kết thúc của bộ đệm). Những điều này vẫn không thay đổi. – Walter

Trả lời

13

C++ chỉ hỗ trợ một cấp độ const. Theo như trình biên dịch , có nghĩa là bit "bit" thực tế trong đối tượng (tức là tính trong sizeof) không thể sửa đổi mà không cần chơi trò chơi (const_cast, v.v.), nhưng mọi thứ khác là công bằng . Trong những ngày đầu của C++ (cuối những năm 1980, đầu những năm 1990) có là rất nhiều cuộc thảo luận về lợi thế thiết kế của bitwise const so với const hợp lý (còn được gọi là Humpty-Dumpty const, vì như Andy Koenig đã từng nói với tôi , khi lập trình viên sử dụng const, điều đó có nghĩa chính xác ý nghĩa của lập trình viên). Sự đồng thuận cuối cùng đã kết hợp với lợi ích của const hợp lý.

Điều này có nghĩa là tác giả của các lớp chứa phải thực hiện một lựa chọn. Các phần tử của phần chứa của vùng chứa có hay không. Nếu chúng là một phần của vùng chứa, thì chúng không thể sửa đổi nếu vùng chứa là const. Không có cách nào để đưa ra lựa chọn; tác giả của vùng chứa phải chọn một hoặc mục còn lại. Ở đây cũng vậy, dường như có sự đồng thuận: các thành phần là một phần của vùng chứa và nếu vùng chứa là const, chúng không thể sửa đổi được. (Có lẽ song song với mảng phong cách C đóng một vai trò ở đây, nếu một mảng C phong cách là const, thì bạn không thể sửa đổi bất kỳ yếu tố của nó.)

Cũng giống như bạn, tôi đã gặp lần khi tôi muốn cấm sửa đổi kích thước của vec-tơ (có lẽ để bảo vệ trình lặp), nhưng không phải của các phần tử của nó. Không có các giải pháp thỏa đáng thực sự là ; điều tốt nhất tôi có thể nghĩ là tạo ra một loại mới, có chứa mutable std::vector và cung cấp chức năng chuyển tiếp tương ứng với ý nghĩa của const Tôi cần trong trường hợp cụ thể này. Và nếu bạn muốn phân biệt ba cấp độ (hoàn toàn const, một phần const và không phải const), bạn sẽ cần nguồn gốc. Lớp cơ sở chỉ hiển thị các hàm const và hàm const một phần hoàn toàn (ví dụ: const int operator[](size_t index) const;int operator[]( size_t index);, nhưng không hiển thị void push_back(int);); các hàm cho phép chèn và loại bỏ một phần tử là chỉ được hiển thị trong lớp dẫn xuất. Khách hàng không nên chèn hoặc xóa các phần tử chỉ được chuyển qua tham chiếu không phải là const đến lớp cơ sở.

+0

+1 tốt đẹp thảo luận. Tuy nhiên, tôi không bị thuyết phục về việc nhà thiết kế lớp container không có sự lựa chọn. Người ta có thể thực hiện một lớp container chứa const-ness của các phần tử của nó như là đối số mẫu và cho phép di chuyển-chuyển đổi (sử dụng con trỏ thông minh) giữa các thùng chứa const và non-const. Tuy nhiên, thiết kế có nguồn gốc cơ sở của bạn trông đơn giản hơn. – Walter

+0

@Walter Nhà thiết kế lớp container có đủ loại lựa chọn. Nhà thiết kế của 'std :: vector' được tạo ra, trong trường hợp này, cái mà dường như có sự đồng thuận chung --- hầu hết các thùng chứa chuẩn trước mà tôi biết là được làm giống nhau. Trên toàn cầu, dường như không có nhu cầu lớn về một container chứa ba mức 'const' (không, một phần hoặc toàn bộ), ngoại trừ trong các trường hợp đặc biệt (ví dụ:' std :: array'). (Trong những ngày trước chuẩn, một trong các lớp mảng của tôi lấy kích thước như một đối số hàm tạo và không có khả năng thay đổi nó sau này.) –

+0

@JamesKanze: Giải thích là tuyệt vời, tuy nhiên hai câu mở đầu tiên ở trên cùng đã cho ấn tượng rằng bitwise const là cái mà C++ hỗ trợ. * (Tôi hy vọng không có quá nhiều lập trình viên C++ là TL; DR.) * Câu mở đầu này là đúng trên trình biên dịch back-end, nhưng front-end chỉ xem xét 'const' là một trợ giúp cú pháp - nó là được sử dụng trong kiểm tra loại, đó là nó. (Nhân tiện, C++ 11 giới thiệu thời gian biên dịch 'constexpr'.) Const-ness logic chỉ tồn tại trong bộ não của các lập trình viên. Một lập trình viên C++ sẽ nhận ra sự khác biệt khi bắt đầu lập trình đa luồng. – rwong

3

Nó không phải là một thiết kế kỳ lạ, đó là một sự lựa chọn rất có chủ ý, và một IMHO phù hợp.

dụ B của bạn không phải là một loại suy tốt cho một std::vector, một loại suy tốt hơn sẽ là:

struct C { 
    int& get(int i) const { return X[i]; } 
    int X[N]; 
}; 

nhưng với sự khác biệt rất hữu ích mà các mảng có thể được thay đổi kích cỡ. Đoạn mã trên không hợp lệ vì lý do tương tự như nguyên gốc của bạn, phần tử (hoặc vector) là khái niệm "thành viên" (các đối tượng phụ về mặt kỹ thuật) của loại chứa, vì vậy bạn không thể sửa đổi chúng thông qua thành viên const chức năng.

Tôi có thể nói rằng const_cast không được chấp nhận và không sử dụng mutable trừ khi là phương sách cuối cùng. Bạn nên hỏi tại sao bạn muốn thay đổi dữ liệu của đối tượng const và xem xét việc làm cho hàm thành viên không phải là const.

+0

Đây là vấn đề giải thích. Nếu bạn xem một 'vectơ' như là một mảng C có thể thay đổi kích thước lớn, thì tôi đồng ý. Tuy nhiên, các hàm 'resize()' etc ở đâu phù hợp với sự tương tự này? Chúng phải nhiều hơn hằng số: bạn muốn có thể cho phép truy cập không vào các phần tử chứ không phải kích thước. Điều này là không thể với 'std :: vector'. Tôi nghĩ cách thông thường được giải quyết là cung cấp trình vòng lặp, thường cho phép thay đổi nội dung, nhưng không cho phép thay đổi. – Walter

+0

Nó chỉ là một tương tự, không dùng nó quá nghĩa đen, anyway 'thay đổi kích cỡ' không phải là const, vì vậy nó được phép đột biến đối tượng. Bạn không chắc chắn những gì bạn có nghĩa là về iterators, nhưng không có container tiêu chuẩn sẽ cung cấp cho bạn một iterator không const để một container const. –

+0

những gì tôi có nghĩa là về iterators là nếu bạn có một (không const) iterator bạn không thể sửa đổi các container. Vì vậy, các container vẫn const trong khi các yếu tố có thể được sửa đổi. – Walter

4

Thật không may, không giống như con trỏ, bạn không thể làm điều gì đó như

std::vector<int> i; 
std::vector<const int>& ref = i; 

Đó là lý do std::vector không thể disambiguate giữa hai loại const khi họ có thể áp dụng, và nó phải được bảo thủ.Tôi, cá nhân, sẽ chọn để làm điều gì đó như

const_cast<int&>(X[i]); 

Edit: Theo commenter khác chỉ chính xác ra, lặp làm mô hình phân đôi này. Nếu bạn đã lưu trữ vector<int>::iterator vào đầu, sau đó bạn có thể bỏ tham chiếu nó theo phương thức const và lấy lại một số không int&. Tôi nghĩ. Nhưng bạn phải cẩn thận về việc vô hiệu.

+1

Việc sử dụng trình vòng lặp cho thấy một giải pháp thú vị: một lớp _view_ chỉ chứa trình lặp đầu tiên và kết thúc, và chỉ cung cấp chức năng giới hạn mà anh ta muốn. (Đối với vấn đề đó, một lớp xem có con trỏ tới vec-tơ cũng sẽ làm được.) –

-1

Tôi khuyên bạn nên sử dụng phương pháp std::vector::at() thay vì const_cast.

+0

Quá tải const của 'vector :: at()' trả về tham chiếu 'const', do đó sẽ không giúp được –