Làm thế nào để quá tải chính xác toán tử << cho một khung hình?


237

Tôi đang viết một thư viện ma trận nhỏ trong C ++ cho các hoạt động ma trận. Tuy nhiên trình biên dịch của tôi phàn nàn, nơi mà trước đó nó không. Mã này đã được để trên kệ trong 6 tháng và ở giữa tôi đã nâng cấp máy tính của mình từ debian etch lên lenny (g ++ (Debian 4.3.2-1.1) 4.3.2) tuy nhiên tôi có cùng một vấn đề trên hệ thống Ubuntu có cùng g ++ .

Đây là phần có liên quan của lớp ma trận của tôi:

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix);
    }
}

Và "thực hiện":

using namespace Math;

std::ostream& Matrix::operator <<(std::ostream& stream, const Matrix& matrix) {

    [...]

}

Đây là lỗi được đưa ra bởi trình biên dịch:

matrix.cpp: 459: error: 'std :: ostream & Math :: Matrix :: toán tử << (std :: ostream &, const Math :: Matrix &)' phải có chính xác một đối số

Tôi hơi bối rối vì lỗi này, nhưng một lần nữa, C ++ của tôi đã bị rỉ sét một chút sau khi thực hiện nhiều Java trong 6 tháng. :-)

Câu trả lời:


127

Bạn đã khai báo chức năng của bạn là friend. Nó không phải là thành viên của lớp. Bạn nên loại bỏ Matrix::khỏi việc thực hiện. friendcó nghĩa là hàm được chỉ định (không phải là thành viên của lớp) có thể truy cập các biến thành viên riêng. Cách bạn triển khai hàm giống như một phương thức cá thể cho Matrixlớp bị sai.


7
Và bạn cũng nên khai báo nó bên trong không gian tên Math (không chỉ với việc sử dụng Math không gian tên).
David Rodríguez - dribeas

1
Tại sao operator<<phải có trong không gian tên của Math? Có vẻ như nó phải ở trong không gian tên toàn cầu. Tôi đồng ý rằng trình biên dịch của tôi muốn nó nằm trong không gian tên của nó Math, nhưng điều đó không có ý nghĩa với tôi.
Đánh dấu Lakata

Xin lỗi, nhưng tôi không biết tại sao chúng ta sử dụng từ khóa bạn bè ở đây? Khi khai báo ghi đè toán tử bạn bè trong một lớp, có vẻ như chúng ta không thể thực hiện với toán tử Matrix :: toán tử << (lasream & os, const Matrix & m). Thay vào đó, chúng ta chỉ cần sử dụng toán tử ghi đè toán tử toàn cầu << lasream & os, const Matrix & m) vậy tại sao thậm chí còn bận tâm khai báo nó trong lớp ngay từ đầu?
Patrick

139

Chỉ cần nói với bạn về một khả năng khác: Tôi thích sử dụng định nghĩa bạn bè cho điều đó:

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix) {
            [...]
        }
    };
}

Hàm sẽ được tự động nhắm mục tiêu vào không gian tên xung quanh Math(mặc dù định nghĩa của nó xuất hiện trong phạm vi của lớp đó) nhưng sẽ không hiển thị trừ khi bạn gọi toán tử << với một đối tượng Matrix sẽ tìm kiếm định nghĩa phụ thuộc đối số tìm định nghĩa toán tử đó. Điều đó đôi khi có thể giúp với các cuộc gọi mơ hồ, vì nó vô hình đối với các loại đối số khác với Ma trận. Khi viết định nghĩa của nó, bạn cũng có thể tham khảo trực tiếp các tên được xác định trong Ma trận và chính Ma trận, mà không cần xác định tên với một số tiền tố dài có thể và cung cấp các tham số mẫu như Math::Matrix<TypeA, N>.


77

Để thêm vào câu trả lời Mehrdad,

namespace Math
{
    class Matrix
    {
       public:

       [...]


    }   
    std::ostream& operator<< (std::ostream& stream, const Math::Matrix& matrix);
}

Trong thực hiện của bạn

std::ostream& operator<<(std::ostream& stream, 
                     const Math::Matrix& matrix) {
    matrix.print(stream); //assuming you define print for matrix 
    return stream;
 }

4
Tôi không hiểu tại sao đây là một cuộc bỏ phiếu, điều này làm rõ rằng bạn có thể khai báo toán tử ở trong không gian tên và thậm chí không phải là một người bạn và làm thế nào bạn có thể khai báo toán tử.
kal

2
Câu trả lời Mehrdad không có đoạn mã nào nên tôi chỉ thêm những gì có thể hoạt động bằng cách di chuyển nó ra ngoài lớp trong chính không gian tên.
kal

Tôi hiểu quan điểm của bạn, tôi chỉ nhìn vào đoạn trích thứ hai của bạn. Nhưng bây giờ tôi thấy bạn đã đưa người điều hành ra khỏi lớp. Cám ơn vì sự gợi ý.
Matthias van der Vlies

7
Nó không chỉ nằm ngoài lớp mà còn được định nghĩa đúng trong không gian tên Math. Ngoài ra, nó có lợi thế bổ sung (có thể không dành cho Ma trận, nhưng với các lớp khác) rằng 'in' có thể là ảo và do đó việc in sẽ xảy ra ở mức độ kế thừa xuất phát nhất.
David Rodríguez - dribeas

68

Giả sử rằng chúng ta đang nói về quá tải operator <<cho tất cả các lớp xuất phát từ std::ostreamđể xử lý Matrixlớp (và không quá tải <<cho Matrixlớp), sẽ có ý nghĩa hơn khi khai báo hàm quá tải bên ngoài không gian tên Math trong tiêu đề.

Chỉ sử dụng chức năng kết bạn nếu chức năng không thể đạt được thông qua các giao diện công cộng.

Ma trận.h

namespace Math { 
    class Matrix { 
        //...
    };  
}
std::ostream& operator<<(std::ostream&, const Math::Matrix&);

Lưu ý rằng quá tải toán tử được khai báo bên ngoài không gian tên.

Ma trận.cpp

using namespace Math;
using namespace std;

ostream& operator<< (ostream& os, const Matrix& obj) {
    os << obj.getXYZ() << obj.getABC() << '\n';
    return os;
}

Mặt khác, nếu chức năng quá tải của bạn không cần phải kết bạn, tức là cần truy cập vào các thành viên riêng tư và được bảo vệ.

Toán.h

namespace Math {
    class Matrix {
        public:
            friend std::ostream& operator<<(std::ostream&, const Matrix&);
    };
}

Bạn cần kèm theo định nghĩa hàm với một khối không gian tên thay vì chỉ using namespace Math;.

Ma trận.cpp

using namespace Math;
using namespace std;

namespace Math {
    ostream& operator<<(ostream& os, const Matrix& obj) {
        os << obj.XYZ << obj.ABC << '\n';
        return os;
    }                 
}

38

Trong C ++ 14, bạn có thể sử dụng mẫu sau để in bất kỳ đối tượng nào có T :: print (std :: ostream &) const; hội viên.

template<class T>
auto operator<<(std::ostream& os, T const & t) -> decltype(t.print(os), os) 
{ 
    t.print(os); 
    return os; 
} 

Trong C ++, 20 khái niệm có thể được sử dụng.

template<typename T>
concept Printable = requires(std::ostream& os, T const & t) {
    { t.print(os) };
};

template<Printable T>
std::ostream& operator<<(std::ostream& os, const T& t) { 
    t.print(os); 
    return os; 
} 

giải pháp thú vị! Một câu hỏi - nơi mà toán tử này nên được khai báo, như trong phạm vi toàn cầu? Tôi giả sử nó nên được hiển thị cho tất cả các loại có thể được sử dụng để templatize nó?
barney

@barney Nó có thể nằm trong không gian tên của riêng bạn cùng với các lớp sử dụng nó.
QuentinUK

bạn không thể quay lại std::ostream&, vì dù sao nó cũng là kiểu trả về?
Jean-Michaël Celerier

5
@ Jean-MichaëlCelerier Dectype đảm bảo rằng toán tử này chỉ được sử dụng khi có t :: print. Nếu không, nó sẽ cố gắng biên dịch thân hàm và đưa ra lỗi biên dịch.
QuentinUK

Các phiên bản đã được thêm vào, được thử nghiệm tại đây godbolt.org/z/u9fGbK
QuentinUK
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.