18

Tôi tin rằng biểu thức T() sẽ tạo một giá trị (theo Chuẩn). Tuy nhiên, đoạn mã sau biên dịch (ít nhất là trên gcc4.0):Tại sao T() = T() được phép?

class T {}; 

int main() 
{ 
    T() = T(); 
} 

Tôi biết kỹ thuật này có thể vì hàm thành viên có thể được gọi vào là tạm thời và trên chỉ là cách gọi các nhà điều hành = trên rvalue tạm được tạo ra từ số T() đầu tiên.

Nhưng khái niệm này giống như gán giá trị mới cho giá trị. Có lý do chính đáng nào tại sao điều này được cho phép không?

Chỉnh sửa: Lý do tôi thấy điều này lạ là bị nghiêm cấm đối với các loại được cài sẵn nhưng được cho phép trên các loại do người dùng xác định. Ví dụ: int(2) = int(3) sẽ không biên dịch vì đó là "giá trị không hợp lệ trong chuyển nhượng".

Vì vậy, tôi đoán câu hỏi thực sự là, hành vi này có phần không nhất quán được xây dựng trong ngôn ngữ vì một lý do nào đó không? Hoặc là nó có cho một số lý do lịch sử? (Ví dụ, nó sẽ là khái niệm âm thanh hơn để chỉ cho phép các hàm thành viên const được gọi trên biểu thức rvalue, nhưng điều đó không thể được thực hiện bởi vì có thể phá vỡ một số mã hiện có.)

Trả lời

3

Đây là lý do tại sao nhiều các lớp trong thư viện chuẩn có thể được thực hiện. Hãy xem xét ví dụ std::bitset<>::operator[]

// bit reference: 
class reference { 
    friend class bitset; 
    reference(); 
public: 
    ˜reference(); 
    reference& operator=(bool x);   // for b[i] = x; 
    reference& operator=(const reference&); // for b[i] = b[j]; 
    bool operator˜() const; // flips the bit 
    operator bool() const; // for x = b[i]; 
    reference& flip();  // for b[i].flip(); 
}; 

reference operator[](size_t pos); // for b[i]; 

Nếu bạn làm bits[i] = true bạn biết chính xác chỉ định một số giá trị cho một rvalue của kiểu lớp. Các proxy được trả về bởi operator[] có thể truy cập các bit được không gian hiệu quả đóng gói thành số nguyên.

+0

Điều này cùng với câu trả lời của FredOverflow trả lời câu hỏi của tôi hoàn toàn. Cảm ơn! – Rimo

13

Điều này được cho phép hoàn toàn do quá tải toán tử, và khả năng bạn có thể quá tải operator = để làm điều gì đó lạ mắt hơn, như in trên bảng điều khiển hoặc khóa một mutex hoặc bất kỳ thứ gì thực sự.

+16

Và không chỉ nhà điều hành = mà còn là người xây dựng. Có rất nhiều công trình kỳ lạ trong C++, nơi trình biên dịch chỉ nhún vai và hy vọng bạn biết bạn đang làm gì. :-) –

7

Có, bạn đang gán giá trị mới cho giá trị. Chính xác hơn, bạn đang gọi hàm thành viên operator = trên giá trị. Vì bạn không sử dụng toán tử gán có sẵn, tại sao bạn cho rằng đây là vấn đề? operator = là một chức năng thành viên của lớp, mà trong hầu hết các khía cạnh là tương tự như bất kỳ chức năng thành viên khác của lớp học, bao gồm cả thực tế là nó có thể được gọi là trên rvalues.

Bạn có lẽ cũng nên tính đến thực tế rằng "là một rvalue" là thuộc tính của biểu thức chứ không phải thuộc tính của đối tượng. Đúng là biểu thức T() đánh giá thành giá trị. Tuy nhiên, đối tượng tạm thời mà biểu thức T() tạo vẫn là đối tượng , có thể được truy cập dưới dạng giá trị. Ví dụ, một số chức năng thành viên khác có thể được gọi vào kết quả của công việc, và nó sẽ thấy (tươi giao) giá trị "mới" của đối tượng tạm thời qua *this vế trái

(T() = T()).some_member_function(); 

Bạn cũng có thể kéo dài tuổi tuổi thọ của tạm thời bằng cách gắn một tham chiếu const vào nó const T& r = T() = T(); và giá trị nhìn thấy qua r sẽ là giá trị "mới" của đối tượng. Khi Johannes ghi chú chính xác trong bình luận của mình, điều này sẽ không đính kèm nó vào một tạm thời.

+0

Có, tôi nghĩ rằng tôi hiểu cơ chế hoạt động như thế nào. Nhưng tôi vẫn còn tò mò 'tại sao' các nhà thiết kế ngôn ngữ cho phép các giá trị r (hoặc các thời gian được tạo ra từ các biểu thức giá trị r) bị đột biến như vậy? Đó có phải là sự giám sát về phía họ hay nó được cho phép một cách có chủ ý (Có thể có một số lý do thực tế đủ hấp dẫn để biện minh cho một hành vi dường như không nhất quán giữa các kiểu dựng sẵn và kiểu người dùng được xác định?) – Rimo

+0

tìm thấy nó ít ngạc nhiên hơn nếu chỉ có các hàm thành viên const được phép được gọi trên các biểu thức rvalue. – Rimo

+3

@Rhimo: Điều đó đánh bại các cấu trúc hợp lý như 'Nguyên tử (std :: cout) << 1 <<" đầu ra không bị gián đoạn "<< std :: endl;' – MSalters

4

Từ một POV, nó không nhất quán, nhưng bạn đang xem nó như thế nào nhất quán: 1) int và các kiểu tích hợp khác vẫn hoạt động giống như trong C, 2) operator = on class-type behavaves như bất kỳ phương pháp nào khác mà không yêu cầu thêm một trường hợp đặc biệt nào khác.

Tính tương thích của C đã được đánh giá cao kể từ khi bắt đầu C++ và C++ được cho là sẽ không có mặt ở đây hôm nay nếu không có nó. Vì vậy, đó là một phần nói chung là một điều tốt.

Điểm thứ hai là understated. Không phải nhà điều hành vỏ đặc biệt = cho phép mã vô nghĩa "làm việc", nhưng tại sao chúng ta quan tâm đến mã vô nghĩa ngay từ đầu? Rác vào, đổ rác ra.Các quy tắc hiện tại cho nó một ý nghĩa xác định (UB ở đây sẽ là xấu) với chi phí không đáng kể, theo như tôi đã từng thấy.

Với các nhà phân tích của tôi, mọi thứ sẽ được đơn giản hóa hơn nữa, vì vậy, int() = int() sẽ được cho phép. C++ 0x bắt đầu quay theo hướng đó với rvalue-tài liệu tham khảo, prvalues ​​vv

+0

"_C++ 0x bắt đầu đi theo hướng đó với tham chiếu rvalue, giá trị gia tăng, v.v ..." Hug? – curiousguy

5

Bạn có thể hạn chế điều hành = để chỉ làm việc trên lvalues ​​trong C++ 0x:

class T 
{ 
public: 
    T& operator=(const T&) & = default; 
};