ผู้ประกอบการทั่วไปที่จะเกินพิกัด
งานส่วนใหญ่ในการบรรทุกเกินพิกัดเป็นรหัสจานหม้อไอน้ำ น่าแปลกใจเล็กน้อยเนื่องจากผู้ประกอบการเป็นเพียงน้ำตาลวากยสัมพันธ์การทำงานจริงของพวกเขาสามารถทำได้โดย (และมักจะถูกส่งต่อไปยัง) ฟังก์ชั่นธรรมดา แต่เป็นสิ่งสำคัญที่คุณต้องได้รับรหัสจานหม้อไอน้ำที่ถูกต้องนี้ หากคุณล้มเหลวรหัสผู้ให้บริการของคุณจะไม่รวบรวมหรือรหัสผู้ใช้ของคุณจะไม่รวบรวมหรือรหัสผู้ใช้ของคุณจะทำงานอย่างแปลกใจ
ผู้ประกอบการที่ได้รับมอบหมาย
มีหลายสิ่งที่ต้องพูดเกี่ยวกับการมอบหมาย อย่างไรก็ตามส่วนใหญ่มีการกล่าวถึงในคำถามที่พบบ่อยเกี่ยวกับการคัดลอกและสลับที่มีชื่อเสียงของ GManดังนั้นฉันจะข้ามไปที่นี่ส่วนใหญ่จะแสดงเฉพาะผู้ดำเนินการที่ได้รับมอบหมายที่สมบูรณ์แบบสำหรับการอ้างอิง:
X& X::operator=(X rhs)
{
swap(rhs);
return *this;
}
ผู้ประกอบการ Bitshift (ใช้สำหรับสตรีม I / O)
ตัวดำเนินการ bitshift <<
และ>>
แม้ว่ายังคงใช้ในการเชื่อมต่อฮาร์ดแวร์สำหรับฟังก์ชันการจัดการบิตที่สืบทอดจาก C ได้กลายเป็นที่แพร่หลายมากขึ้นเป็นตัวดำเนินการอินพุตและเอาต์พุตสตรีมที่โอเวอร์โหลดในแอพพลิเคชันส่วนใหญ่ สำหรับคำแนะนำมากไปในฐานะผู้ดำเนินการจัดการบิตดูส่วนด้านล่างของตัวดำเนินการทางคณิตศาสตร์แบบไบนารี สำหรับการนำรูปแบบที่กำหนดเองของคุณเองและตรรกะการแยกวิเคราะห์เมื่อวัตถุของคุณใช้กับ iostreams ให้ดำเนินการต่อ
ตัวดำเนินการสตรีมในหมู่ตัวดำเนินการโอเวอร์โหลดที่พบมากที่สุดคือตัวดำเนินการไบนารีมัดซึ่งไวยากรณ์ระบุไม่มีข้อ จำกัด ว่าควรจะเป็นสมาชิกหรือไม่ใช่สมาชิก เนื่องจากพวกเขาเปลี่ยนอาร์กิวเมนต์ด้านซ้ายของพวกเขา (พวกเขาเปลี่ยนสถานะของกระแส) พวกเขาควรจะดำเนินการในฐานะสมาชิกประเภทตัวถูกดำเนินการด้านซ้ายตามกฎของหัวแม่มือ อย่างไรก็ตามตัวถูกดำเนินการทางด้านซ้ายของพวกเขาคือกระแสข้อมูลจากไลบรารีมาตรฐานและในขณะที่ตัวประมวลผลกระแสข้อมูลและตัวดำเนินการอินพุตที่กำหนดโดยไลบรารีมาตรฐานส่วนใหญ่จะถูกกำหนดเป็นสมาชิกของคลาสกระแสข้อมูลอย่างแน่นอนเมื่อคุณดำเนินการเอาท์พุท ไม่สามารถเปลี่ยนประเภทการสตรีมของไลบรารีมาตรฐาน นั่นเป็นเหตุผลที่คุณจำเป็นต้องใช้โอเปอเรเตอร์เหล่านี้สำหรับประเภทของคุณเองเป็นฟังก์ชั่นที่ไม่ใช่สมาชิก รูปแบบบัญญัติของทั้งสองคือ:
std::ostream& operator<<(std::ostream& os, const T& obj)
{
// write obj to stream
return os;
}
std::istream& operator>>(std::istream& is, T& obj)
{
// read obj from stream
if( /* no valid object of T found in stream */ )
is.setstate(std::ios::failbit);
return is;
}
เมื่อใช้งานการoperator>>
ตั้งค่าสถานะของสตรีมด้วยตนเองเป็นสิ่งจำเป็นเฉพาะเมื่อการอ่านตัวเองสำเร็จ แต่ผลลัพธ์ไม่ใช่สิ่งที่คาดหวัง
ผู้ประกอบการโทรฟังก์ชั่น
ผู้ประกอบการเรียกฟังก์ชั่นที่ใช้ในการสร้างฟังก์ชั่นวัตถุที่เรียกว่า functors จะต้องกำหนดเป็นฟังก์ชั่นสมาชิกดังนั้นจึงมักจะมีthis
ข้อโต้แย้งโดยนัยของฟังก์ชั่นสมาชิก นอกเหนือจากนี้มันสามารถโอเวอร์โหลดเพื่อรับจำนวนอาร์กิวเมนต์เพิ่มเติมใด ๆ รวมถึงศูนย์
นี่คือตัวอย่างของไวยากรณ์:
class foo {
public:
// Overloaded call operator
int operator()(const std::string& y) {
// ...
}
};
การใช้งาน:
foo f;
int a = f("hello");
ตลอดทั้งไลบรารีมาตรฐาน C ++ ฟังก์ชันของวัตถุจะถูกคัดลอกเสมอ วัตถุฟังก์ชันของคุณเองควรถูกคัดลอก หากฟังก์ชั่นวัตถุจำเป็นต้องใช้ข้อมูลที่มีราคาแพงในการคัดลอกมันจะดีกว่าที่จะเก็บข้อมูลนั้นไว้ที่อื่นและมีฟังก์ชั่นวัตถุอ้างอิง
ผู้ประกอบการเปรียบเทียบ
ผู้ประกอบการเปรียบเทียบไบนารีมัดควรตามกฎของหัวแม่มือที่จะนำมาใช้เป็นฟังก์ชั่นที่ไม่ใช่สมาชิก1 การปฏิเสธคำนำหน้า unary !
ควรใช้ (ตามกฎเดียวกัน) ในฐานะสมาชิกฟังก์ชัน (แต่โดยทั่วไปแล้วไม่ใช่ความคิดที่ดีที่จะโอเวอร์โหลด)
อัลกอริธึมของไลบรารีมาตรฐาน (เช่นstd::sort()
) และประเภท (เช่นstd::map
) จะคาดหวังoperator<
ให้แสดงอยู่เสมอ อย่างไรก็ตามผู้ใช้ประเภทของคุณจะคาดหวังว่าผู้ให้บริการรายอื่นทั้งหมดจะอยู่ด้วยเช่นกันดังนั้นหากคุณกำหนดoperator<
ให้แน่ใจว่าได้ปฏิบัติตามกฎพื้นฐานที่สามของผู้ประกอบการมากไปและยังกำหนดผู้ประกอบการเปรียบเทียบแบบบูลอื่น ๆ วิธีที่เป็นที่ยอมรับในการใช้งานคือ:
inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}
สิ่งสำคัญที่ควรทราบที่นี่คือผู้ดำเนินการเหล่านี้เพียงสองคนเท่านั้นที่ทำอะไรจริง ๆ คนอื่น ๆ กำลังส่งต่อข้อโต้แย้งไปยังทั้งสองคนนี้เพื่อทำงานจริง
ไวยากรณ์สำหรับการโหลดตัวดำเนินการบูลีนไบนารีบูลีนที่เหลืออยู่ ( ||
, &&
) เป็นไปตามกฎของตัวดำเนินการเปรียบเทียบ แต่ก็เป็นมากไม่น่าที่คุณจะพบกรณีการใช้งานที่เหมาะสมสำหรับทั้ง2
1 เช่นเดียวกับกฎของหัวแม่มือบางครั้งอาจมีเหตุผลที่จะทำลายสิ่งนี้เช่นกัน ถ้าเป็นเช่นนั้นอย่าลืมว่าตัวถูกดำเนินการทางด้านซ้ายของตัวดำเนินการเปรียบเทียบแบบไบนารีซึ่งสำหรับฟังก์ชันของสมาชิกจะ*this
ต้องเป็นconst
เช่นกัน ดังนั้นตัวดำเนินการเปรียบเทียบที่นำมาใช้เป็นฟังก์ชันสมาชิกจะต้องมีลายเซ็นนี้:
bool operator<(const X& rhs) const { /* do actual comparison with *this */ }
(สังเกตconst
ที่ท้าย)
2 ควรสังเกตว่าเวอร์ชั่นในตัว||
และ&&
ใช้ซีแมนทิกส์ช็อตคัท ในขณะที่ผู้ใช้กำหนดคน (เพราะพวกเขาเป็นน้ำตาล syntactic สำหรับวิธีการโทร) ไม่ใช้ความหมายทางลัด ผู้ใช้จะคาดหวังว่าโอเปอเรเตอร์เหล่านี้จะมีซีแมนทิกส์ช็อตคัทและรหัสของพวกเขาอาจขึ้นอยู่กับมันดังนั้นจึงไม่แนะนำให้กำหนดอย่างแน่นอน
ตัวดำเนินการทางคณิตศาสตร์
ตัวดำเนินการทางคณิตศาสตร์แบบเอกนารี
โอเปอเรเตอร์การเพิ่มและการลดค่า unary มีทั้งคำนำหน้าและรสชาติของคำนำหน้า หากต้องการบอกอีกตัวหนึ่งตัวแปร postfix จะใช้อาร์กิวเมนต์ดัมมี int เพิ่มเติม หากคุณเพิ่มหรือลดจำนวนมากเกินควรใช้ทั้งคำนำหน้าและเวอร์ชัน postfix เสมอ นี่คือการใช้งานแบบบัญญัติของการเพิ่มขึ้นการลดลงเป็นไปตามกฎเดียวกัน:
class X {
X& operator++()
{
// do actual increment
return *this;
}
X operator++(int)
{
X tmp(*this);
operator++();
return tmp;
}
};
โปรดทราบว่าชุดตัวเลือก postfix นั้นมีการใช้งานในรูปแบบของคำนำหน้า นอกจากนี้โปรดทราบว่า postfix จะทำสำเนาเพิ่มเติม 2
การบรรทุกเกินพิกัดลบและบวกไม่ธรรมดามากและอาจหลีกเลี่ยงได้ดีที่สุด หากจำเป็นพวกเขาอาจจะโอเวอร์โหลดเป็นฟังก์ชันสมาชิก
2 โปรดทราบว่าตัวแปร postfix ทำงานได้มากกว่าและดังนั้นจึงมีประสิทธิภาพน้อยกว่าการใช้งานกว่าตัวแปร prefix นี่เป็นเหตุผลที่ดีที่จะชอบการเพิ่มค่า prefix มากกว่าการเพิ่ม postfix ในขณะที่คอมไพเลอร์สามารถเพิ่มประสิทธิภาพการทำงานเพิ่มเติมของการเพิ่ม postfix สำหรับชนิดในตัวพวกเขาอาจไม่สามารถทำแบบเดียวกันกับประเภทที่ผู้ใช้กำหนด (ซึ่งอาจเป็นสิ่งที่ดูเป็นผู้ทำรายการ) เมื่อคุณคุ้นเคยi++
ก็จะยากที่จะจำ++i
แทนเมื่อi
ไม่มีประเภทในตัว (รวมทั้งคุณต้องเปลี่ยนรหัสเมื่อเปลี่ยนประเภท) ดังนั้นจึงเป็นการดีกว่าที่จะสร้างนิสัยของเสมอ ใช้การเพิ่มคำนำหน้ายกเว้นว่าจำเป็นต้องใช้ postfix อย่างชัดเจน
ตัวดำเนินการทางคณิตศาสตร์แบบไบนารี
สำหรับผู้ประกอบการเลขคณิตไบนารีอย่าลืมที่จะปฏิบัติตามกฎพื้นฐานตัวที่สามของการบรรทุกเกินพิกัด: หากคุณให้บริการ+
นอกจากนี้+=
หากคุณจัดหาให้-
อย่าละเว้น-=
ฯลฯ แอนดรูว์นิกกล่าวว่าเป็นคนแรกที่สังเกตว่าการมอบหมายผสม ผู้ประกอบการสามารถใช้เป็นฐานสำหรับคู่ที่ไม่ได้ประกอบของพวกเขา นั่นคือผู้ประกอบการ+
จะดำเนินการในแง่ของ+=
, -
มีการใช้งานในแง่ของ-=
ฯลฯ
ตามกฎของเราหัวแม่มือ+
และสหายของมันควรจะไม่ใช่สมาชิกในขณะที่คู่หูที่ได้รับมอบหมายผสมของพวกเขา ( +=
ฯลฯ ) เปลี่ยนอาร์กิวเมนต์ซ้ายของพวกเขาควรจะเป็นสมาชิก นี่คือรหัสที่เป็นแบบอย่างสำหรับ+=
และ+
; ผู้ประกอบการเลขคณิตไบนารีอื่น ๆ ควรนำมาใช้ในลักษณะเดียวกัน:
class X {
X& operator+=(const X& rhs)
{
// actual addition of rhs to *this
return *this;
}
};
inline X operator+(X lhs, const X& rhs)
{
lhs += rhs;
return lhs;
}
operator+=
ส่งคืนผลลัพธ์ตามการอ้างอิงขณะที่operator+
ส่งคืนสำเนาผลลัพธ์ แน่นอนว่าการส่งคืนการอ้างอิงมักจะมีประสิทธิภาพมากกว่าการส่งคืนสำเนา แต่ในกรณีที่operator+
ไม่มีวิธีแก้ไขการคัดลอก เมื่อคุณเขียนa + b
คุณคาดหวังว่าผลลัพธ์จะเป็นค่าใหม่ซึ่งเป็นสาเหตุที่operator+
ต้องส่งคืนค่าใหม่ 3
นอกจากนี้ยังทราบว่าoperator+
จะใช้ตัวถูกดำเนินการด้านซ้ายโดยการคัดลอกมากกว่าโดยอ้างอิง const เหตุผลนี้เป็นเช่นเดียวกับเหตุผลที่ให้operator=
การโต้แย้งต่อสำเนา
ตัวดำเนินการจัดการบิต~
&
|
^
<<
>>
ควรนำมาใช้ในลักษณะเดียวกับตัวดำเนินการทางคณิตศาสตร์ อย่างไรก็ตาม (ยกเว้นสำหรับการบรรทุกเกินพิกัด<<
และ>>
สำหรับเอาท์พุทและอินพุต) มีกรณีการใช้งานที่สมเหตุสมผลน้อยมากสำหรับการใช้งานมากไป
3 อีกครั้งบทเรียนที่ได้จากนี้คือa += b
โดยทั่วไปมีประสิทธิภาพมากกว่าa + b
และควรเป็นที่ต้องการถ้าเป็นไปได้
การห้อยอาร์เรย์
ตัวดำเนินการตัวห้อยของอาร์เรย์เป็นตัวดำเนินการไบนารีซึ่งจะต้องนำมาใช้เป็นสมาชิกของคลาส มันถูกใช้สำหรับประเภทภาชนะเหมือนที่อนุญาตให้เข้าถึงองค์ประกอบข้อมูลของพวกเขาโดยคีย์ รูปแบบบัญญัติของการให้สิ่งเหล่านี้คือ:
class X {
value_type& operator[](index_type idx);
const value_type& operator[](index_type idx) const;
// ...
};
นอกเสียจากว่าคุณไม่ต้องการให้ผู้ใช้ในชั้นเรียนของคุณสามารถเปลี่ยนองค์ประกอบข้อมูลที่ส่งคืนโดยoperator[]
(ในกรณีนี้คุณสามารถละเว้นตัวแปรที่ไม่ใช่ const) คุณควรให้ทั้งสองตัวแปรของโอเปอเรเตอร์
ถ้า value_type เป็นที่รู้จักกันในการอ้างถึงชนิดของบิวด์อินตัวแปร const ของโอเปอเรเตอร์ควรส่งคืนสำเนาแทนการอ้างอิง const:
class X {
value_type& operator[](index_type idx);
value_type operator[](index_type idx) const;
// ...
};
ตัวดำเนินการสำหรับประเภทที่เหมือนตัวชี้
สำหรับการกำหนดตัววนซ้ำหรือพอยน์เตอร์สมาร์ทของคุณเองคุณจะต้องโอเวอร์โหลดตัวดำเนินการ dereference ของคำนำหน้า unary *
และตัวดำเนินการเข้าถึงสมาชิกของตัวชี้ไบนารี infix ->
:
class my_ptr {
value_type& operator*();
const value_type& operator*() const;
value_type* operator->();
const value_type* operator->() const;
};
โปรดทราบว่าสิ่งเหล่านี้เช่นกันมักจะต้องมีทั้งรุ่น const และรุ่นที่ไม่ใช่แบบ const สำหรับ->
โอเปอเรเตอร์หากvalue_type
เป็นประเภทclass
(หรือstruct
หรือunion
) อีกประเภทหนึ่งoperator->()
จะถูกเรียกซ้ำจนกว่าจะoperator->()
ส่งคืนค่าประเภทที่ไม่ใช่คลาส
ผู้ให้บริการ address-of unary ไม่ควรรับภาระมากเกินไป
สำหรับoperator->*()
ดูคำถามนี้ มันไม่ค่อยได้ใช้และทำให้เกิดการโอเวอร์โหลดบ่อยครั้ง ในความเป็นจริงแม้ตัววนซ้ำจะไม่ทำงานหนักเกินไป
ดำเนินการต่อไปยังผู้ให้บริการการแปลง