2013-06-11 18 views
15

Tại sao chương trình C++ dưới đây xuất hiện "ACCA"? Tại sao operator int() được gọi hai lần?Quá trình toán tử C++ quá tải

#include "stdafx.h" 
#include <iostream> 

using namespace std; 

class Base { 
public: 
    Base(int m_var=1):i(m_var){ 
     cout<<"A"; 
    } 
    Base(Base& Base){ 
     cout<<"B"; 
     i=Base.i; 
    } 
    operator int() { 
     cout<<"C"; 
     return i; 
    } 
private: 
    int i; 
}; 

int main() 
{ 
    Base obj; 
    obj = obj+obj; 
    return 0; 
} 
+0

FYI; Đây là một cấu trúc được tạo ra để tạo ra ACCA, như một bài tập cho người đọc. –

Trả lời

30

Thứ nhất, dòng này:

Base obj; 

default-cấu trúc đối tượng obj bằng cách chọn các nhà xây dựng chấp nhận một số nguyên, với giá trị mặc định 1. Điều này chịu trách nhiệm cho A đầu tiên được in ra đầu ra tiêu chuẩn.

Sau đó, biểu thức này:

obj + obj 

Yêu cầu chọn một tình trạng quá tải khả thi của operator +. Trong trường hợp này, kể từ khi obj có chuyển đổi do người dùng xác định thành int, số được chọn tích hợp operator + được chọn và cả hai đối số được chuyển đổi thành int. Điều này chịu trách nhiệm cho hai số C được in tới đầu ra tiêu chuẩn.

Sau đó, việc chuyển nhượng để obj trong:

obj = obj + obj 

nhu cầu để gọi ngầm tạo operator = cho Base. Các ngầm tạo operator = có chữ ký:

Base& operator = (Base const&); 

Điều này có nghĩa rằng các biểu hiện ở phía bên phải của dấu bằng, đó là loại int, phải được chuyển đổi thành một Base đối tượng tạm thời từ đó obj được gán (tài liệu tham khảo tham số của operator = được tạo ngầm được ràng buộc tạm thời này).

Nhưng việc tạo ra tạm thời này từ một int lần lượt đòi hỏi cách gọi việc xây dựng chuyển đổi của Base chấp nhận một int một lần nữa, có trách nhiệm cho phần thứ hai A được in vào đầu ra tiêu chuẩn.

9

operator int() được gọi hai lần vì bạn chưa quá tải operator+. Trình biên dịch không biết cách thêm Base vào Base, vì vậy chúng được chuyển đổi thành int (vì bạn đã dạy nó cách thực hiện), mà nó biết cách thực hiện. Các bản in mã sau ADA:

#include <iostream> 

using namespace std; 

class Base { 
public: 
    Base(int m_var=1):i(m_var){ 
     cout<<"A"; 
    } 
    Base(Base& Base){ 
     cout<<"B"; 
     i=Base.i; 
    } 
    operator int() { 
     cout<<"C"; 
     return i; 
    } 
    int operator+(Base& Base) 
    { 
     cout<<"D"; 
     return i+Base.i; 
    } 
private: 
    int i; 
}; 

int main() 
{ 
    Base obj; 
    obj = obj+obj; 
    return 0; 
} 
1

Bạn tham khảo obj hai lần trong biểu thức obj+obj và mỗi tài liệu tham khảo như vậy phải được chuyển đổi sang một số nguyên. Nó không phải là không thể (mặc dù nó là một ý tưởng khủng khiếp) rằng một chuyển đổi như vậy có thể là "trạng thái" - có nghĩa là, nó có thể bằng cách thiết kế trả về một giá trị khác nhau mỗi khi nó được gọi. Sau khi tất cả, giá trị đại diện bởi obj có thể thay đổi (nó có thể là một truy cập hoặc một cái gì đó như thế). Vì vậy, trình biên dịch phải đánh giá nó afresh cho mỗi tài liệu tham khảo.

+0

Vì 'toán tử int()' không được đánh dấu 'const', chữ ký cho trình biên dịch khá trực tiếp rằng nó sẽ mong đợi cuộc gọi sửa đổi trạng thái của đối tượng. Tuy nhiên, ngay cả đối với một toán tử const, cuộc gọi thứ hai sẽ không được phép tối ưu hóa chính xác bởi vì có thể có một cái gì đó giống như 'cout' trong nó (và đây thực sự là nó). – celtschk

+0

Và chúng ta có thể nghĩ về một hàm có tác dụng phụ (như viết thành đầu ra tiêu chuẩn) khi trả về một phiên bản mới của vũ trụ mỗi khi nó được gọi, đó là một cách khác để giải thích "trả về một giá trị khác nhau mỗi khi nó được gọi là". –

1

Gọi một lần cho mỗi toán hạng, không quan trọng nếu nó là cùng một ví dụ.

obj = obj+obj; 

Nó "phôi" toán hạng đầu tiên vào int và sau đó là toán hạng hai.

Bạn cần quá tải toán tử + nếu bạn muốn tránh quá trình truyền ngầm này.

1

Gọi số operator int() hai lần vì cần chuyển đổi cả obj thành int trước khi có thể thêm chúng.

5

Khi bạn xây dựng các đối tượng, bạn nhận được là người đầu tiên "A":

Base obj; 

Khi bạn chỉ định để thêm obj+obj, trình biên dịch cần phải tìm ra một cách để sử dụng + trên obj. Vì bạn đã không ghi đè lên một operator+ cho Base, chuyển đổi sang int() được gọi cho mỗi vế của phương trình:

obj+obj 

này in "CC".

Sau đó, bạn gán cho obj, mà là loại Base, vì vậy các nhà xây dựng có thể chấp nhận một int (i + i từ các nhà điều hành int()) đang chạy, mà bản in "A":

obj = obj+obj; // Assignment prints "A" 
5
obj = obj+obj; 
     ^^^--------obj converted to int here 
      ^^^----obj converted to int here 
^^^^^------------Base(int) ctor and default operator= called here 

Quá tải các toán tử truyền thường không phải là ý tưởng hay trừ khi bạn hiểu chi phí và biết rằng lợi ích trong trường hợp cụ thể của bạn lớn hơn.

1

A đầu tiên xuất phát từ

Base obj;

Hai Cs đến từ chuyển đổi obj trong obj+obj để int như bạn không quá tải operator +.

A cuối cùng đến từ obj = chuyển đổi kết quả int thành obj.

2

cách chương trình C++ tính toán thành "ACCA"?

Chữ cái đầu tiên được hiển thị là 'A'. Kết quả này có liên quan đến dòng này:

Base obj; 

... trong đó bạn đang tạo một phiên bản mới của Base.

Dòng tiếp theo là một chút phức tạp:

obj = obj+obj; 

Thông thường, điều này được phiên dịch sang obj.operator+(obj), nhưng bạn không có nhà điều hành + quá tải trong lớp Base, vì vậy dịch này không hợp lệ. Khả năng còn lại là toán tử + thực sự là toán tử thêm số.

Và có, điều này là có thể, vì bạn đã cung cấp dàn diễn viên cho int. Vì vậy, nó có thể chuyển đổi mỗi thuật ngữ của phương trình trong một int ... và do đó operator int được gọi là hai lần. Số lần thực tế operator int được gọi phụ thuộc vào kích hoạt tối ưu hóa. Ví dụ, trình biên dịch có thể nhận ra rằng cả hai điều khoản là như nhau, và sau đó tạo ra một tạm thời mới khi operator int đã được gọi là lần đầu tiên. Trong trường hợp đó, bạn sẽ thấy CA thay vì CC.

Cuối cùng, biểu thức gán obj.operator=(temp) được thực hiện. Và tại đây từ khóa là temp. Để cho defaultoperator= hoạt động, vì nó không bị quá tải, bạn phải có đối tượng Base ở bên phải. Nó thực sự là có thể có nó, vì Base sử dụng một int để tạo các phiên bản mới. Được rồi, vì vậy kết quả của obj + obj là một int (nói nó được gọi là 'x') số lượng, và trình biên dịch tạo ra một đối tượng tạm thời của lớp Base mà được xây dựng với số x, như dòng sau được thực hiện:

Base temp(x); 

Đó là cách chữ cái cuối cùng được xem là 'A'. Một lần nữa, nhiều trình biên dịch có thể tránh việc xây dựng các thời gian trên một số trường hợp, do đó, có thể không nhìn thấy chữ 'A' ở cuối.

Lưu ý rằng dòng này:

obj = obj + obj 

Được như vậy, bị phân hủy trong:

int x = ((int) obj) + ((int) obj); 
Base temp(x); 
obj = temp; 

Các hướng dẫn chính thức có kết quả như là nếu bộ nhớ trong đó obj ngồi sẽ được bận rộn với các nội dung của temp (đó là vai trò của hàm tạo bản sao mặc định, thực hiện operator= cho mỗi thành viên của lớp, xem lại 'quy tắc ba').

Quá tải toán tử liên quan đến rất nhiều vấn đề có thể không được dự đoán nếu bạn không có kiến ​​thức sâu sắc về ngôn ngữ, như bạn có thể thấy. Cũng cần lưu ý rằng các ngôn ngữ như Java hoàn toàn ngăn cản việc sử dụng nó, trong khi C# cho phép nó từ một góc nhìn được kiểm soát.