2011-06-10 47 views
23

Tôi đã đọc hiệu quả C++ 3rd Edition được viết bởi Scott Meyers.Tại sao toán tử ++ trả về giá trị không const?

Mục 3 của sách, "Sử dụng const bất cứ khi nào có thể", cho biết nếu chúng tôi muốn ngăn không cho giá trị không được gán cho giá trị trả về của hàm vô tình, loại trả về phải là const.

Ví dụ, hàm increment cho iterator:

const iterator iterator::operator++(int) { 
    ... 
} 

Sau đó, một số tai nạn được ngăn chặn.

iterator it; 

// error in the following, same as primitive pointer 
// I wanted to compare iterators 
if (it++ = iterator()) { 
    ... 
} 

Tuy nhiên, vòng lặp như std::vector::iterator trong GCC không trả lại giá trị const.

vector<int> v; 
v.begin()++ = v.begin(); // pass compiler check 

Có một số lý do cho việc này không?

+3

Bạn không nên sử dụng ++ ở bất kỳ bên nào của biểu thức so sánh. –

+2

Tuyệt vời có bao nhiêu câu trả lời sai. –

+3

Bây giờ nó là một thủ thuật cũ, vì nó ngăn cản việc di chuyển-xây dựng. Cách hiện đại để làm điều đó là định nghĩa 'toán tử =' như mặc định bằng cách sử dụng một 'vòng loại'. –

Trả lời

11

Tôi khá chắc chắn rằng điều này là bởi vì nó sẽ chơi tàn phá với tham chiếu rvalue và bất kỳ loại decltype. Mặc dù các tính năng này không có trong C++ 03, chúng đã được biết đến.

Quan trọng hơn, tôi không tin rằng bất kỳ hàm chuẩn nào trả về giá trị const, nó có thể là thứ không được xem xét cho đến sau khi tiêu chuẩn được xuất bản. Ngoài ra, giá trị const thường không được coi là Điều Đúng Để Làm ™. Không phải tất cả việc sử dụng các hàm thành viên không phải là const đều không hợp lệ và việc trả về các giá trị const là ngăn cản chúng.

Ví dụ,

auto it = ++vec.begin(); 

là hoàn toàn hợp lệ, và quả thật, ngữ nghĩa có giá trị, nếu không chính xác mong muốn. Hãy xem xét lớp học của tôi cung cấp các chuỗi phương pháp.

class ILikeMethodChains { 
public: 
    int i; 
    ILikeMethodChains& SetSomeInt(int param) { 
     i = param; 
     return *this; 
    } 
}; 
ILikeMethodChains func() { ... } 
ILikeMethodChains var = func().SetSomeInt(1); 

Điều đó có nên không được phép chỉ vì có thể, đôi khi, chúng tôi có thể gọi hàm không có ý nghĩa? Tất nhiên là không rồi. Hoặc làm thế nào về "swaptimization"?

std::string func() { return "Hello World!"; } 
std::string s; 
func().swap(s); 

Đây sẽ là bất hợp pháp nếu func() sản xuất một biểu thức const - nhưng đó là hoàn toàn hợp lệ và thực sự, giả định rằng thực hiện std::string 's không phân bổ bất kỳ bộ nhớ trong constructor mặc định, cả hai nhanh chóng và dễ đọc/đọc được.

Điều bạn nên nhận ra là quy tắc rvalue/lvalue C++ 03 thực sự không có ý nghĩa. Họ là, có hiệu quả, chỉ một phần nướng, và tối thiểu cần thiết để không cho phép một số sai lầm trắng trợn trong khi cho phép một số quyền có thể. Các quy tắc rvalue C++ 0x là an toàn hơn và nhiều hơn nữa hoàn thành.

+0

không phải là 'const rvalue' một nghiên cứu? Tôi có nghĩa là, không phải là rvalue, mà không có một địa chỉ, const theo định nghĩa? – davka

+1

@davka: Không, chúng không thể liên kết với các tham chiếu không phải const. Bản thân họ không phải là bất kỳ cách nào cả. – Puppy

+0

+1 cho đoạn cuối – Josh

1

Nếu it không phải là const, tôi mong đợi *(++it) để cung cấp cho tôi quyền truy cập dễ dàng vào nội dung mà nó đại diện.

Tuy nhiên, dereferencing chỉ cho phép truy cập không thể thay đổi đối với thứ mà nó đại diện. [chỉnh sửa: không, this is wrong too. Tôi thực sự bỏ cuộc ngay bây giờ!]

Đây là lý do duy nhất tôi có thể nghĩ đến.

Như bạn một cách đúng đắn chỉ ra, sau đây là vô hình thành vì ++ trên sản lượng nguyên thủy một rvalue (mà không thể được trên LHS):

int* p = 0; 
(p++)++; 

Vì vậy, dường như là một cái gì đó về sự mâu thuẫn trong ngôn ngữ ở đây.

+6

Đây là * cũng * không phải những gì OP hỏi (tình cờ, các nhận xét khác giải thích về điều này ở đâu? Đã xóa?). Vòng lặp 'for' thậm chí không sử dụng hành vi được đề cập trong câu hỏi. –

+0

@Konrad: Vâng, đúng vậy. Đoạn cuối cùng của tôi giải thích sự khác biệt giữa những gì OP nghĩ rằng một 'const iterator' sẽ làm, và những gì nó thực sự sẽ làm .. và lý do cho những gì nó thực sự làm (đó là những gì anh ta yêu cầu). Vòng lặp hoàn toàn sử dụng hành vi: câu hỏi đặc biệt thảo luận về 'op ++', mà (nên) là không thể đối với một đối tượng' const'. Khó lặp lại với thứ gì đó không thể được sử dụng để lặp lại. –

+0

@Tom Tôi nghĩ rằng bạn đang nhầm lẫn nhưng bạn có thể thuyết phục tôi đối diện với tôi bằng cách giải thích những gì bạn có ý nghĩa trong câu trả lời của bạn. Ngoài ra, hãy chú ý tiêu đề câu hỏi đã thay đổi. Câu hỏi hoàn toàn không liên quan đến các trình vòng lặp (đó chỉ là một ví dụ), đó là về kiểu trả về của 'toán tử ++'. Và nhận xét của bạn cũng sai: 'it' * không phải là *' const'. Giá trị trả về của '++ it', mặc dù, là. Không có vấn đề ở đây. –

-1

EDIT: Điều này không thực sự trả lời câu hỏi như được nêu trong các nhận xét. Tôi sẽ chỉ để lại bài đăng ở đây trong trường hợp nó hữu ích dù sao ...

Tôi nghĩ đây là vấn đề hợp nhất cú pháp đối với giao diện có thể sử dụng tốt hơn. Khi cung cấp các chức năng thành viên như vậy mà không phân biệt tên và chỉ cho phép cơ chế giải quyết tình trạng quá tải xác định phiên bản chính xác mà bạn ngăn chặn (hoặc ít nhất là cố gắng) lập trình viên làm các lo ngại liên quan đến const.

Tôi biết điều này có vẻ mâu thuẫn, đặc biệt là ví dụ của bạn. Nhưng nếu bạn nghĩ về hầu hết các trường hợp sử dụng nó có ý nghĩa. Thực hiện thuật toán STL như std::equal. Cho dù container của bạn có liên tục hay không, bạn luôn có thể mã hóa một cái gì đó như bool e = std::equal(c.begin(), c.end(), c2.begin()) mà không cần phải suy nghĩ về phiên bản bắt đầu và kết thúc phù hợp.

Đây là cách tiếp cận chung trong STL. Hãy nhớ rằng operator[] ... Có trong tâm trí rằng các thùng chứa sẽ được sử dụng với các thuật toán, điều này là chính đáng. Mặc dù nó cũng đáng chú ý rằng trong một số trường hợp, bạn vẫn có thể cần phải xác định một trình lặp với một phiên bản phù hợp (iterator hoặc const_iterator).

Vâng, đây chỉ là những gì đến với tâm trí của tôi ngay bây giờ. Tôi không chắc chắn mức độ thuyết phục của nó là ...

Lưu ý bên: Cách chính xác để sử dụng trình lặp không đổi là thông qua const_iterator typedef.

+2

Điều này thực sự không liên quan. 'std :: equal' sẽ lấy giá trị vòng lặp theo giá trị, hoàn toàn phủ nhận bất kỳ constness nào mà giá trị iterator cụ thể có thể có. – Puppy

+0

@DeadMG - Có thể bạn đã bỏ lỡ vị trí ...?Vấn đề là nếu bạn đang ở trong một bối cảnh trong đó bạn có một tham chiếu đến const để chứa một container và bạn cần phải gọi std :: bằng nhau, ví dụ, bạn không phải lo lắng về việc gọi một c.beginConstVersion như vậy() chức năng, bạn chỉ có thể gọi c.begin() và trình biên dịch sẽ làm các trick. –

+0

@Itcmelo: Ngoại trừ việc đó được thực hiện bằng cách trả về 'const_iterator', không phải là' const iterator'. Câu hỏi và ngữ nghĩa đằng sau nó không liên quan gì đến việc truy cập vào thùng chứa - chúng về việc truy cập vào giá trị * được trả về bởi một hàm. Đó là sự thật không phân biệt nếu nó là một tham chiếu hoặc một tham chiếu const/mutable. – Puppy

Các vấn đề liên quan