Toán tử << nên được thực hiện như một người bạn hoặc là một chức năng thành viên?


129

Về cơ bản đó là câu hỏi, có cách nào "đúng" để thực hiện operator<<không? Đọc điều này tôi có thể thấy rằng một cái gì đó như:

friend bool operator<<(obj const& lhs, obj const& rhs);

được ưu tiên cho một cái gì đó như

ostream& operator<<(obj const& rhs);

Nhưng tôi không thể hiểu tại sao tôi nên sử dụng cái này hay cái kia.

Trường hợp cá nhân của tôi là:

friend ostream & operator<<(ostream &os, const Paragraph& p) {
    return os << p.to_str();
}

Nhưng tôi có thể làm được:

ostream & operator<<(ostream &os) {
    return os << paragraph;
}

Lý do nào tôi nên dựa trên quyết định này?

Lưu ý :

 Paragraph::to_str = (return paragraph) 

trong đó đoạn văn là một chuỗi.


4
BTW có lẽ bạn nên thêm const vào chữ ký của các thành viên
Motti

4
Tại sao trả về bool từ toán tử <<? Bạn đang sử dụng nó như là một toán tử luồng hoặc như là một sự quá tải của sự thay đổi bitwise?
Martin York

Câu trả lời:


120

Vấn đề ở đây là trong cách giải thích của bạn về bài viết bạn liên kết .

Bình đẳng

Bài viết này là về ai đó đang gặp vấn đề xác định chính xác các toán tử quan hệ bool.

Nhà điều hành:

  • Bình đẳng == và! =
  • Mối quan hệ <> <=> =

Các toán tử này sẽ trả về một bool vì chúng đang so sánh hai đối tượng cùng loại. Thông thường dễ nhất để định nghĩa các toán tử này là một phần của lớp. Điều này là do một lớp tự động là một người bạn của chính nó nên các đối tượng thuộc Kiểu Đoạn có thể kiểm tra lẫn nhau (thậm chí cả các thành viên riêng khác).

Có một đối số để thực hiện các chức năng đứng miễn phí này vì điều này cho phép tự động chuyển đổi chuyển đổi cả hai bên nếu chúng không cùng loại, trong khi các chức năng thành viên chỉ cho phép rhs được tự động chuyển đổi. Tôi thấy đây là một đối số người làm giấy vì bạn không thực sự muốn chuyển đổi tự động xảy ra ở nơi đầu tiên (thường). Nhưng nếu đây là điều bạn muốn (tôi không khuyến nghị) thì làm cho các bộ so sánh đứng tự do có thể là lợi thế.

Truyền phát

Các toán tử luồng:

  • toán tử << đầu ra
  • toán tử >> đầu vào

Khi bạn sử dụng chúng làm toán tử luồng (thay vì dịch chuyển nhị phân), tham số đầu tiên là luồng. Vì bạn không có quyền truy cập vào đối tượng luồng (không phải của bạn để sửa đổi) nên chúng không thể là toán tử thành viên, chúng phải ở bên ngoài lớp. Do đó, họ phải là bạn của lớp hoặc có quyền truy cập vào một phương thức công khai sẽ phát trực tuyến cho bạn.

Nó cũng là truyền thống cho các đối tượng này để trả về một tham chiếu đến một đối tượng luồng để bạn có thể xâu chuỗi các hoạt động luồng với nhau.

#include <iostream>

class Paragraph
{
    public:
        explicit Paragraph(std::string const& init)
            :m_para(init)
        {}

        std::string const&  to_str() const
        {
            return m_para;
        }

        bool operator==(Paragraph const& rhs) const
        {
            return m_para == rhs.m_para;
        }
        bool operator!=(Paragraph const& rhs) const
        {
            // Define != operator in terms of the == operator
            return !(this->operator==(rhs));
        }
        bool operator<(Paragraph const& rhs) const
        {
            return  m_para < rhs.m_para;
        }
    private:
        friend std::ostream & operator<<(std::ostream &os, const Paragraph& p);
        std::string     m_para;
};

std::ostream & operator<<(std::ostream &os, const Paragraph& p)
{
    return os << p.to_str();
}


int main()
{
    Paragraph   p("Plop");
    Paragraph   q(p);

    std::cout << p << std::endl << (p == q) << std::endl;
}

19
Tại sao là operator<< private:?
Matt Clarkson

47
@MattClarkson: Không phải. Do đó, nó là một khai báo hàm bạn bè không phải là một phần của lớp và do đó không bị ảnh hưởng bởi các chỉ định truy cập. Tôi thường đặt các khai báo hàm bạn bè bên cạnh dữ liệu mà họ truy cập.
Martin York

12
Tại sao nó cần phải là một chức năng thân thiện, nếu bạn đang sử dụng chức năng công cộng để truy cập dữ liệu? Xin lỗi, nếu câu hỏi là ngu ngốc.
Semyon Danilov

4
@SemyonDanilov: Tại sao bạn lại phá vỡ đóng gói và thêm getters! freiendlà một cách để mở rộng giao diện công cộng mà không phá vỡ đóng gói. Đọc lập trình
Martin York

3
@LokiAstari Nhưng chắc chắn đó là một đối số để xóa to_str hoặc đặt nó ở chế độ riêng tư. Như hiện tại, toán tử phát trực tuyến không phải là một người bạn, vì nó chỉ sử dụng các chức năng công cộng.
deworde

53

Bạn không thể thực hiện nó như là một hàm thành viên, bởi vì thistham số ẩn là phía bên trái của <<-operator. (Do đó, bạn sẽ cần thêm nó dưới dạng hàm thành viên vào ostreamlớp. Không tốt :)

Bạn có thể làm nó như là một chức năng miễn phí mà không cần friending? Đó là những gì tôi thích, bởi vì nó cho thấy rõ rằng đây là sự tích hợp ostreamvà không phải là chức năng cốt lõi của lớp bạn.


1
"không phải là một chức năng cốt lõi của lớp học của bạn." Đó là "người bạn" nghĩa là gì. Nếu đó là chức năng cốt lõi, nó sẽ ở trong lớp chứ không phải bạn bè.
xaxxon

1
@xaxxon Tôi nghĩ rằng câu đầu tiên của tôi giải thích tại sao trong trường hợp này không thể thêm chức năng làm chức năng thành viên. Một friendhàm có các quyền giống như một hàm thành viên ( đây là ý friendnghĩa của nó), vì vậy với tư cách là người dùng của lớp, tôi sẽ phải tự hỏi tại sao nó lại cần điều đó. Đây là điểm khác biệt mà tôi đang cố gắng thực hiện với từ ngữ "chức năng cốt lõi".
Magnus Hoff

32

Nếu có thể, như các chức năng không phải thành viên và không bạn bè.

Theo mô tả của Herb Sutter và Scott Meyers, thích các chức năng không phải là thành viên không phải bạn bè với các chức năng thành viên, để giúp tăng đóng gói.

Trong một số trường hợp, như các luồng C ++, bạn sẽ không có lựa chọn và phải sử dụng các hàm không phải là thành viên.

Tuy nhiên, điều đó không có nghĩa là bạn phải biến các hàm này thành bạn của các lớp của mình: Các hàm này vẫn có thể truy cập lớp của bạn thông qua các hàm truy cập lớp của bạn. Nếu bạn thành công trong việc viết các chức năng này theo cách này, thì bạn đã thắng.

Giới thiệu về nguyên mẫu toán tử << và >>

Tôi tin rằng các ví dụ bạn đưa ra trong câu hỏi của bạn là sai. Ví dụ;

ostream & operator<<(ostream &os) {
    return os << paragraph;
}

Tôi thậm chí không thể bắt đầu nghĩ làm thế nào phương pháp này có thể hoạt động trong một luồng.

Dưới đây là hai cách để thực hiện các toán tử << và >>.

Giả sử bạn muốn sử dụng một đối tượng giống như luồng loại T.

Và bạn muốn trích xuất / chèn từ / vào T dữ liệu có liên quan của đối tượng thuộc loại Đoạn.

Các nguyên mẫu hàm toán tử << và >> chung

Đầu tiên là chức năng:

// T << Paragraph
T & operator << (T & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// T >> Paragraph
T & operator >> (T & p_oInputStream, const Paragraph & p_oParagraph)
{
   // do the extraction of p_oParagraph
   return p_oInputStream ;
}

Các nguyên mẫu chung của toán tử << và >>

Thứ hai là phương thức:

// T << Paragraph
T & T::operator << (const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return *this ;
}

// T >> Paragraph
T & T::operator >> (const Paragraph & p_oParagraph)
{
   // do the extraction of p_oParagraph
   return *this ;
}

Lưu ý rằng để sử dụng ký hiệu này, bạn phải mở rộng khai báo lớp T. Đối với các đối tượng STL, điều này là không thể (bạn không cần phải sửa đổi chúng ...).

Và nếu T là một luồng C ++ thì sao?

Dưới đây là các nguyên mẫu của cùng một toán tử << và >> cho các luồng C ++.

Đối với basic_istream và basic_ostream chung

Lưu ý rằng đó là trường hợp của luồng, vì bạn không thể sửa đổi luồng C ++, bạn phải thực hiện các chức năng. Có nghĩa là một cái gì đó như:

// OUTPUT << Paragraph
template <typename charT, typename traits>
std::basic_ostream<charT,traits> & operator << (std::basic_ostream<charT,traits> & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// INPUT >> Paragraph
template <typename charT, typename traits>
std::basic_istream<charT,traits> & operator >> (std::basic_istream<charT,traits> & p_oInputStream, const CMyObject & p_oParagraph)
{
   // do the extract of p_oParagraph
   return p_oInputStream ;
}

Đối với char istream và Ostream

Đoạn mã sau sẽ chỉ hoạt động đối với các luồng dựa trên char.

// OUTPUT << A
std::ostream & operator << (std::ostream & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// INPUT >> A
std::istream & operator >> (std::istream & p_oInputStream, const Paragraph & p_oParagraph)
{
   // do the extract of p_oParagraph
   return p_oInputStream ;
}

Rhys Ulerich nhận xét về thực tế mã dựa trên char nhưng là "chuyên môn hóa" của mã chung ở trên nó. Tất nhiên, Rhys đã đúng: Tôi không khuyên bạn nên sử dụng ví dụ dựa trên char. Nó chỉ được đưa ra ở đây vì nó đơn giản hơn để đọc. Vì nó chỉ khả thi nếu bạn chỉ làm việc với các luồng dựa trên char, nên bạn nên tránh nó trên các nền tảng nơi mã wchar_t là phổ biến (tức là trên Windows).

Hy vọng điều này sẽ giúp.


Không phải mã templated basic_istream và basic_ostream chung của bạn đã bao gồm các phiên bản dành riêng cho std :: Ostream- và std :: iux vì hai phiên bản sau chỉ là tức thời của phiên bản cũ sử dụng ký tự sao?
Rhys Ulerich

@Rhys Ulerich: Tất nhiên rồi. Tôi chỉ sử dụng phiên bản chung, templated, nếu chỉ vì trên Windows, bạn phải xử lý cả mã char và wchar_t. Ưu điểm duy nhất của phiên bản thứ hai là xuất hiện đơn giản hơn phiên bản thứ nhất. Tôi sẽ làm rõ bài viết của tôi về điều đó.
paercebal

10

Nó nên được thực hiện như một chức năng miễn phí, không có bạn bè, đặc biệt là, giống như hầu hết mọi thứ hiện nay, đầu ra chủ yếu được sử dụng để chẩn đoán và ghi nhật ký. Thêm const accessor cho tất cả những thứ cần đi vào đầu ra, và sau đó có bộ đầu ra chỉ cần gọi những cái đó và thực hiện định dạng.

Tôi thực sự đã thực hiện để thu thập tất cả các chức năng miễn phí đầu ra của bộ phát này trong một tệp thực hiện và tiêu đề "bộ đệm", nó giữ cho chức năng phụ đó cách xa mục đích thực sự của các lớp.


7

Chữ ký:

bool operator<<(const obj&, const obj&);

Có vẻ khá nghi ngờ, điều này không phù hợp với streamquy ước cũng như quy ước bitwise nên có vẻ như một trường hợp lạm dụng toán tử quá tải, operator <nên quay lại boolnhưng operator <<có lẽ nên trả lại một cái gì đó khác.

Nếu bạn muốn nói như vậy:

ostream& operator<<(ostream&, const obj&); 

Sau đó, vì bạn không thể thêm chức năng vào một ostreamcách cần thiết, nên chức năng phải là một chức năng miễn phí, cho dù nó có friendhay không phụ thuộc vào những gì nó phải truy cập (nếu không cần phải truy cập các thành viên riêng tư hoặc được bảo vệ thì không cần phải thực hiện bạn bè).


Cần đề cập đến quyền truy cập để sửa đổi ostreamkhi sử dụng đơn ostream.operator<<(obj&)đặt hàng; do đó chức năng miễn phí. Mặt khác, loại người dùng cần là loại hơi nước để phù hợp với quyền truy cập.
wulfgarpro

2

Chỉ cần hoàn thành, tôi muốn nói thêm rằng bạn thực sự có thể tạo một toán tử ostream& operator << (ostream& os)bên trong một lớp và nó có thể hoạt động. Từ những gì tôi biết, không nên sử dụng nó, bởi vì nó rất phức tạp và không trực quan.

Giả sử chúng ta có mã này:

#include <iostream>
#include <string>

using namespace std;

struct Widget
{
    string name;

    Widget(string _name) : name(_name) {}

    ostream& operator << (ostream& os)
    {
        return os << name;
    }
};

int main()
{
    Widget w1("w1");
    Widget w2("w2");

    // These two won't work
    {
        // Error: operand types are std::ostream << std::ostream
        // cout << w1.operator<<(cout) << '\n';

        // Error: operand types are std::ostream << Widget
        // cout << w1 << '\n';
    }

    // However these two work
    {
        w1 << cout << '\n';

        // Call to w1.operator<<(cout) returns a reference to ostream&
        w2 << w1.operator<<(cout) << '\n';
    }

    return 0;
}

Vì vậy, để tổng hợp nó - bạn có thể làm điều đó, nhưng bạn có lẽ không nên :)


0

toán tử bạn bè = quyền bình đẳng như lớp

friend std::ostream& operator<<(std::ostream& os, const Object& object) {
    os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
    return os;
}

0

operator<< thực hiện như một chức năng bạn bè:

#include <iostream>
#include <string>
using namespace std;

class Samp
{
public:
    int ID;
    string strName; 
    friend std::ostream& operator<<(std::ostream &os, const Samp& obj);
};
 std::ostream& operator<<(std::ostream &os, const Samp& obj)
    {
        os << obj.ID<<   << obj.strName;
        return os;
    }

int main()
{
   Samp obj, obj1;
    obj.ID = 100;
    obj.strName = "Hello";
    obj1=obj;
    cout << obj <<endl<< obj1;

} 

ĐẦU RA:
100 Xin chào
100 Xin chào

Đây chỉ có thể là chức năng kết bạn vì đối tượng nằm ở phía bên phải operator<<và đối số coutnằm ở phía bên trái. Vì vậy, đây không thể là một hàm thành viên cho lớp, nó chỉ có thể là một hàm bạn bè.


tôi không nghĩ có một cách để viết nó như là một chức năng thành viên !!
Rohit Vipin Mathews

Tại sao mọi thứ đều táo bạo. Hãy để tôi loại bỏ điều này.
Sebastian Mach
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.