ตัวดำเนินการ << ควรดำเนินการในฐานะเพื่อนหรือเป็นฟังก์ชันของสมาชิก?


129

นั่นคือคำถามโดยพื้นฐานแล้วมีวิธีที่ "ถูกต้อง" ในการนำไปใช้operator<<หรือไม่? อ่านสิ่งนี้ฉันจะเห็นว่าสิ่งที่ชอบ:

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

เป็นที่ต้องการของบางสิ่งเช่น

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

แต่ฉันไม่สามารถเข้าใจได้ว่าทำไมฉันจึงควรใช้อย่างใดอย่างหนึ่ง

กรณีส่วนตัวของฉันคือ:

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

แต่ฉันอาจจะทำได้:

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

ฉันควรใช้เหตุผลใดในการตัดสินใจนี้

หมายเหตุ :

 Paragraph::to_str = (return paragraph) 

โดยที่ย่อหน้าเป็นสตริง


4
BTW คุณควรเพิ่ม const ให้กับลายเซ็นของฟังก์ชันสมาชิก
Motti

4
ทำไมต้องส่งคืนบูลจากตัวดำเนินการ <<? คุณใช้มันเป็นตัวดำเนินการสตรีมหรือเป็นส่วนเกินของการเลื่อนบิตหรือไม่?
Martin York

คำตอบ:


120

นี่คือปัญหาในการตีความของคุณของบทความที่คุณเชื่อมโยง

ความเท่าเทียมกัน

บทความนี้เกี่ยวกับคนที่มีปัญหาในการกำหนดตัวดำเนินการความสัมพันธ์บูลอย่างถูกต้อง

ตัวดำเนินการ:

  • ความเท่าเทียม == และ! =
  • ความสัมพันธ์ <> <=> =

ตัวดำเนินการเหล่านี้ควรส่งคืนบูลเนื่องจากกำลังเปรียบเทียบวัตถุสองชิ้นที่มีประเภทเดียวกัน โดยปกติจะง่ายที่สุดในการกำหนดตัวดำเนินการเหล่านี้เป็นส่วนหนึ่งของคลาส เนื่องจากชั้นเรียนเป็นเพื่อนของตัวเองโดยอัตโนมัติดังนั้นออบเจ็กต์ประเภทย่อหน้าจึงสามารถตรวจสอบกันและกันได้ (แม้แต่สมาชิกส่วนตัวของแต่ละคน)

มีข้อโต้แย้งในการสร้างฟังก์ชันยืนอิสระเหล่านี้เนื่องจากจะช่วยให้การแปลงอัตโนมัติแปลงทั้งสองด้านหากไม่ใช่ประเภทเดียวกันในขณะที่ฟังก์ชันสมาชิกอนุญาตให้แปลง rhs โดยอัตโนมัติเท่านั้น ฉันพบว่านี่เป็นข้อโต้แย้งของคนทำกระดาษเนื่องจากคุณไม่ต้องการให้การแปลงอัตโนมัติเกิดขึ้นตั้งแต่แรก (โดยปกติ) แต่ถ้านี่คือสิ่งที่คุณต้องการ (ฉันไม่แนะนำ) การทำให้ตัวเปรียบเทียบยืนฟรีจะเป็นประโยชน์

สตรีมมิ่ง

ตัวดำเนินการสตรีม:

  • ตัวดำเนินการ << เอาต์พุต
  • ตัวดำเนินการ >> อินพุต

เมื่อคุณใช้สิ่งเหล่านี้เป็นตัวดำเนินการสตรีม (แทนที่จะเป็นไบนารีกะ) พารามิเตอร์แรกคือสตรีม เนื่องจากคุณไม่มีสิทธิ์เข้าถึงอ็อบเจ็กต์สตรีม (ไม่ใช่ของคุณที่จะแก้ไข) สิ่งเหล่านี้จึงไม่สามารถเป็นตัวดำเนินการสมาชิกได้จึงต้องอยู่ภายนอกคลาส ดังนั้นพวกเขาจะต้องเป็นเพื่อนในชั้นเรียนหรือสามารถเข้าถึงวิธีการสาธารณะที่จะทำการสตรีมให้คุณ

นอกจากนี้ยังเป็นแบบดั้งเดิมสำหรับอ็อบเจ็กต์เหล่านี้ในการส่งคืนการอ้างอิงไปยังอ็อบเจ็กต์สตรีมเพื่อให้คุณสามารถเชื่อมโยงการดำเนินการสตรีมเข้าด้วยกัน

#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
ทำไมoperator<< private:?
Matt Clarkson

47
@MattClarkson: ไม่ใช่ การประกาศฟังก์ชันเพื่อนจึงไม่ได้เป็นส่วนหนึ่งของคลาสและไม่ได้รับผลกระทบจากตัวระบุการเข้าถึง โดยทั่วไปฉันวางคำประกาศฟังก์ชันเพื่อนไว้ข้างข้อมูลที่พวกเขาเข้าถึง
Martin York

12
เหตุใดจึงต้องเป็นฟังก์ชันที่เป็นมิตรหากคุณใช้ฟังก์ชันสาธารณะเพื่อเข้าถึงข้อมูล ขออภัยถ้าคำถามงี่เง่า
Semyon Danilov

4
@SemyonDanilov: ทำไมคุณถึงทำลายการห่อหุ้มและเพิ่มตัวรับ! freiendเป็นวิธีการขยายส่วนติดต่อสาธารณะโดยไม่ทำลายการห่อหุ้ม อ่านprogrammers.stackexchange.com/a/99595/12917
Martin York

3
@LokiAstari แต่นั่นเป็นข้อโต้แย้งสำหรับการลบ to_str หรือทำให้เป็นส่วนตัว ตามที่กล่าวมาผู้ให้บริการสตรีมมิงไม่จำเป็นต้องเป็นเพื่อนเนื่องจากใช้ฟังก์ชันสาธารณะเท่านั้น
น้ำค้าง

53

คุณไม่สามารถทำเป็นฟังก์ชันสมาชิกได้เนื่องจากthisพารามิเตอร์โดยนัยอยู่ทางซ้ายมือของ<<-operator (ดังนั้นคุณจะต้องเพิ่มเป็นฟังก์ชันสมาชิกในostream-class ไม่ดี :)

คุณสามารถทำมันเป็นฟังก์ชันฟรีโดยไม่ต้องใช้friendมันได้หรือไม่? นั่นคือสิ่งที่ฉันชอบเพราะมันทำให้ชัดเจนว่านี่เป็นการรวมเข้ากับostreamไม่ใช่ฟังก์ชันหลักของชั้นเรียนของคุณ


1
"ไม่ใช่ฟังก์ชันหลักของชั้นเรียนของคุณ" นั่นคือสิ่งที่ "เพื่อน" หมายถึง หากเป็นฟังก์ชันหลักก็จะอยู่ในชั้นเรียนไม่ใช่เพื่อน
xaxxon

1
@xaxxon ฉันคิดว่าประโยคแรกของฉันอธิบายว่าทำไมจึงเป็นไปไม่ได้ในกรณีนี้ที่จะเพิ่มฟังก์ชันเป็นฟังก์ชันสมาชิก friendฟังก์ชั่นที่มีสิทธิเช่นเดียวกับฟังก์ชั่นสมาชิก ( นี้คือสิ่งที่friendหมายถึง) ดังนั้นในฐานะผู้ใช้ของชั้นที่ฉันจะต้องแปลกใจว่าทำไมมันจะต้องว่า นี่คือความแตกต่างที่ฉันพยายามสร้างขึ้นโดยใช้คำว่า "ฟังก์ชันหลัก"
Magnus Hoff

32

ถ้าเป็นไปได้เป็นฟังก์ชันที่ไม่ใช่สมาชิกและไม่ใช่เพื่อน

ตามที่อธิบายโดย Herb Sutter และ Scott Meyers เลือกใช้ฟังก์ชันที่ไม่ใช่เพื่อนกับฟังก์ชันของสมาชิกเพื่อช่วยเพิ่มการห่อหุ้ม

ในบางกรณีเช่นสตรีม C ++ คุณจะไม่มีทางเลือกและต้องใช้ฟังก์ชันที่ไม่ใช่สมาชิก

แต่ถึงกระนั้นก็ไม่ได้หมายความว่าคุณจะต้องทำให้ฟังก์ชันเหล่านี้เป็นเพื่อนในชั้นเรียนของคุณฟังก์ชันเหล่านี้ยังสามารถเข้าถึงชั้นเรียนของคุณผ่านทางตัวเข้าถึงชั้นเรียนของคุณ หากคุณประสบความสำเร็จในการเขียนฟังก์ชันเหล่านั้นด้วยวิธีนี้คุณก็จะชนะ

เกี่ยวกับตัวดำเนินการ << และ >> ต้นแบบ

ฉันเชื่อว่าตัวอย่างที่คุณให้ไว้ในคำถามของคุณไม่ถูกต้อง ตัวอย่างเช่น;

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

ฉันคิดไม่ออกด้วยซ้ำว่าวิธีนี้ใช้ได้ผลในสตรีมอย่างไร

ต่อไปนี้เป็นสองวิธีในการใช้ตัวดำเนินการ << และ >>

สมมติว่าคุณต้องการใช้วัตถุคล้ายสตรีมประเภท T

และคุณต้องการแยก / แทรกจาก / ลงใน T ข้อมูลที่เกี่ยวข้องของวัตถุของคุณประเภทย่อหน้า

ตัวดำเนินการทั่วไป << และ >> ต้นแบบฟังก์ชัน

สิ่งแรกที่เป็นหน้าที่:

// 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 ;
}

ตัวดำเนินการทั่วไป << และ >> ต้นแบบวิธีการ

สิ่งที่สองเป็นวิธีการ:

// 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 ;
}

โปรดทราบว่าในการใช้สัญกรณ์นี้คุณต้องขยายการประกาศคลาสของ T สำหรับวัตถุ STL สิ่งนี้เป็นไปไม่ได้ (คุณไม่ควรแก้ไข ... )

แล้วถ้า T เป็นสตรีม C ++ ล่ะ?

นี่คือต้นแบบของตัวดำเนินการ << และ >> เดียวกันสำหรับสตรีม C ++

สำหรับ basic_istream ทั่วไปและ basic_ostream

โปรดทราบว่าเป็นกรณีของสตรีมเนื่องจากคุณไม่สามารถแก้ไขสตรีม C ++ ได้คุณต้องใช้ฟังก์ชัน ซึ่งหมายความว่า:

// 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 ;
}

สำหรับ char istream และ ostream

รหัสต่อไปนี้จะใช้ได้กับสตรีมที่ใช้ถ่านเท่านั้น

// 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 แสดงความคิดเห็นเกี่ยวกับความจริงที่ว่ารหัสที่ใช้ถ่านนั้นเป็นเพียง "ความเชี่ยวชาญพิเศษ" ของรหัสทั่วไปที่อยู่ข้างบน แน่นอน Rhys พูดถูก: ฉันไม่แนะนำให้ใช้ตัวอย่างที่ใช้ถ่าน ให้ไว้ที่นี่เท่านั้นเพราะอ่านง่ายกว่า เนื่องจากจะทำงานได้เฉพาะในกรณีที่คุณทำงานกับสตรีมที่ใช้ถ่านเท่านั้นคุณควรหลีกเลี่ยงการใช้งานบนแพลตฟอร์มที่รหัส wchar_t เป็นเรื่องธรรมดา (เช่นบน Windows)

หวังว่านี่จะช่วยได้


โค้ด templated basic_istream และ basic_ostream ทั่วไปของคุณไม่ได้ครอบคลุมเวอร์ชันเฉพาะ std :: ostream- และ std :: istream เนื่องจากสองรุ่นหลังเป็นเพียงการสร้างอินสแตนซ์ของแบบเดิมที่ใช้ตัวอักษรหรือไม่
Rhys Ulerich

@Rhys Ulerich: แน่นอน ฉันใช้เฉพาะเวอร์ชันทั่วไปเท่านั้นหากเป็นเพียงเพราะใน Windows คุณต้องจัดการกับทั้งรหัสถ่านและ wchar_t ข้อดีประการเดียวของรุ่นที่สองคือการดูเหมือนง่ายกว่ารุ่นแรก ฉันจะชี้แจงโพสต์ของฉันเกี่ยวกับเรื่องนั้น
paercebal

10

ควรใช้เป็นฟังก์ชันฟรีที่ไม่ใช่เพื่อนโดยเฉพาะอย่างยิ่งหากเช่นเดียวกับสิ่งต่างๆในปัจจุบันผลลัพธ์ส่วนใหญ่จะใช้สำหรับการวินิจฉัยและการบันทึก เพิ่มตัวเข้าถึง const สำหรับทุกสิ่งที่ต้องเข้าสู่เอาต์พุตจากนั้นให้เอาต์พุตเพียงแค่เรียกสิ่งเหล่านั้นและทำการจัดรูปแบบ

ฉันได้รวบรวมฟังก์ชั่นที่ไม่มีเอาต์พุตของ ostream เหล่านี้ไว้ในไฟล์ส่วนหัวและการใช้งาน "ostreamhelpers" จริง ๆ แล้วมันช่วยให้ฟังก์ชันรองนั้นห่างไกลจากวัตถุประสงค์ที่แท้จริงของคลาส


7

ลายเซ็น:

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

ดูเหมือนจะเป็นที่น่าสงสัยว่าสิ่งนี้ไม่เข้ากับstreamอนุสัญญาหรืออนุสัญญาแบบบิตดังนั้นจึงดูเหมือนกรณีของผู้ปฏิบัติงานที่ใช้งานในทางที่ผิดมากเกินไปoperator <ควรส่งคืนboolแต่operator <<น่าจะส่งคืนอย่างอื่น

หากคุณหมายถึงเช่นนั้นให้พูดว่า:

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

จากนั้นเนื่องจากคุณไม่สามารถเพิ่มฟังก์ชั่นได้ostreamโดยความจำเป็นฟังก์ชันจะต้องเป็นฟังก์ชันฟรีไม่ว่าจะเป็นfriendหรือไม่ขึ้นอยู่กับสิ่งที่ต้องเข้าถึง (หากไม่จำเป็นต้องเข้าถึงสมาชิกส่วนตัวหรือได้รับการป้องกันก็ไม่จำเป็นต้องทำ เพื่อน)


เป็นมูลค่าการกล่าวถึงการเข้าถึงเพื่อแก้ไขostreamจะต้องใช้เมื่อใช้ostream.operator<<(obj&)คำสั่ง ด้วยเหตุนี้ฟังก์ชันฟรี มิฉะนั้นประเภทผู้ใช้จะต้องเป็นประเภทไอน้ำเพื่อรองรับการเข้าถึง
wulfgarpro

2

เพื่อประโยชน์ในการเรียนจบฉันอยากจะเพิ่มว่าคุณสามารถสร้างตัวดำเนินการostream& operator << (ostream& os)ภายในชั้นเรียนและมันสามารถทำงานได้ จากสิ่งที่ฉันรู้ว่ามันไม่ใช่ความคิดที่ดีที่จะใช้เพราะมันซับซ้อนและไม่เข้าใจง่าย

สมมติว่าเรามีรหัสนี้:

#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;
}

เพื่อสรุป - คุณสามารถทำได้ แต่ส่วนใหญ่ไม่ควร :)


0

ตัวดำเนินการเพื่อน = สิทธิเท่าเทียมกันในชั้นเรียน

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

0

operator<< ใช้เป็นฟังก์ชันเพื่อน:

#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;

} 

เอาท์พุท:
100 สวัสดี
100 สวัสดี

ฟังก์ชันนี้สามารถเป็นเพื่อนได้เท่านั้นเนื่องจากวัตถุอยู่ทางด้านขวามือของoperator<<และอาร์กิวเมนต์coutอยู่ทางด้านซ้ายมือ ดังนั้นนี่จึงไม่สามารถเป็นฟังก์ชันสมาชิกของชั้นเรียนได้ แต่เป็นฟังก์ชันเพื่อนเท่านั้น


ฉันไม่คิดว่าจะมีวิธีเขียนสิ่งนี้ในฐานะสมาชิก funtion !!
Rohit Vipin Mathews

ทำไมทุกอย่างเป็นตัวหนา ขอฉันลบสิ่งนี้ออก
Sebastian Mach
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.