การใช้ตัวดำเนินการเปรียบเทียบผ่าน 'tuple' และ 'tie' เป็นความคิดที่ดีหรือไม่?


98

(หมายเหตุ: tupleและtieสามารถนำมาจาก Boost หรือ C ++ 11)
เมื่อเขียนโครงสร้างขนาดเล็กที่มีเพียงสององค์ประกอบบางครั้งฉันมักจะเลือก a std::pairเนื่องจากมีการทำสิ่งสำคัญทั้งหมดสำหรับประเภทข้อมูลนั้นแล้วเช่นoperator<การจัดลำดับที่เข้มงวด - อ่อนแอ .
ข้อเสียคือชื่อตัวแปรที่ค่อนข้างไร้ประโยชน์ แม้ว่าฉันจะสร้างสิ่งนั้นขึ้นมาเองtypedefแต่ฉันก็จำไม่ได้ว่า 2 วันต่อมาว่าfirstอะไรsecondคืออะไรโดยเฉพาะอย่างยิ่งถ้าทั้งสองเป็นประเภทเดียวกัน สิ่งนี้จะแย่ลงไปอีกสำหรับสมาชิกมากกว่าสองคนเนื่องจากการทำรังนั้นpairค่อนข้างแย่มาก
อีกทางเลือกหนึ่งคือไฟล์tupleไม่ว่าจะจาก Boost หรือ C ++ 11 แต่ก็ไม่ได้ดูดีและชัดเจนขึ้น ดังนั้นฉันจึงกลับไปเขียนโครงสร้างด้วยตัวเองรวมถึงตัวดำเนินการเปรียบเทียบที่จำเป็น
เนื่องจากโดยเฉพาะอย่างยิ่งoperator<อาจเป็นเรื่องยุ่งยากฉันจึงคิดที่จะหลีกเลี่ยงความยุ่งเหยิงทั้งหมดนี้โดยอาศัยการดำเนินการที่กำหนดไว้สำหรับtuple:

ตัวอย่างoperator<เช่นสำหรับการสั่งซื้อที่เข้มงวด - อ่อนแอ:

bool operator<(MyStruct const& lhs, MyStruct const& rhs){
  return std::tie(lhs.one_member, lhs.another, lhs.yet_more) <
         std::tie(rhs.one_member, rhs.another, rhs.yet_more);
}

( tieทำให้tupleของT&การอ้างอิงจากการขัดแย้งผ่าน.)


แก้ไข : ข้อเสนอแนะจาก @DeadMG ถึงการสืบทอดแบบส่วนตัวtupleไม่ใช่เรื่องเลวร้าย แต่ก็มีข้อเสียอยู่บ้าง:

  • หากผู้ดำเนินการมีสถานะอิสระ (อาจเป็นเพื่อนกัน) ฉันต้องสืบทอดต่อสาธารณะ
  • ด้วยการแคสต์ฟังก์ชัน / ตัวดำเนินการของฉัน ( operator=โดยเฉพาะ) สามารถข้ามได้อย่างง่ายดาย
  • ด้วยtieวิธีแก้ปัญหาฉันสามารถละทิ้งสมาชิกบางคนได้หากพวกเขาไม่สำคัญสำหรับการสั่งซื้อ

มีข้อบกพร่องใด ๆ ในการใช้งานนี้ที่ฉันต้องพิจารณาหรือไม่?


1
ดูสมเหตุสมผลกับฉันมาก ...
ildjarn

1
นั่นเป็นความคิดที่ฉลาดมากแม้ว่าจะไม่ได้เลื่อนออกไป ฉันจะต้องตรวจสอบเรื่องนี้
templatetypedef

นี่ดูสมเหตุสมผลทีเดียว ข้อผิดพลาดเดียวที่ฉันคิดได้ตอนนี้คือtieไม่สามารถใช้กับสมาชิกบิตฟิลด์ได้
Ise Wisteria

4
ฉันชอบไอเดียนี้! หากการtie(...)โทรจะซ้ำกันในตัวดำเนินการต่างๆ (=, ==, <ฯลฯ ) คุณสามารถเขียนวิธีการอินไลน์ส่วนตัวmake_tuple(...)เพื่อห่อหุ้มสิ่งนั้นแล้วเรียกจากที่อื่น ๆ เช่นในreturn lhs.make_tuple() < rhs.make_tuple();(แม้ว่าประเภทการส่งคืนจาก วิธีการที่อาจจะมีความสนุกสนานในการประกาศ)!
Aldo

13
@aldo: C ++ 14 เพื่อช่วยเหลือ! auto tied() const{ return std::tie(the, members, here); }
Xeo

คำตอบ:


61

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


17
ฉันไม่สามารถจินตนาการกรณีที่tuple<>'s operator<จะเป็นใด ๆ ช้ากว่าที่เขียนด้วยลายมือหนึ่ง
ildjarn

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

7
@JohannesD: ฉันสามารถสนับสนุนคำให้การนั้นได้ทำแบบเดียวกันครั้งเดียว
เห็น

สิ่งนี้รับประกันการสั่งซื้อที่เข้มงวดหรือไม่? อย่างไร?
CinCout

5

ฉันเจอปัญหาเดียวกันนี้และวิธีแก้ปัญหาของฉันใช้เทมเพลตตัวแปร c ++ 11 รหัสมาที่นี่:

ส่วน. h:

/***
 * Generic lexicographical less than comparator written with variadic templates
 * Usage:
 *   pass a list of arguments with the same type pair-wise, for intance
 *   lexiLessthan(3, 4, true, false, "hello", "world");
 */
bool lexiLessthan();

template<typename T, typename... Args>
bool lexiLessthan(const T &first, const T &second, Args... rest)
{
  if (first != second)
  {
    return first < second;
  }
  else
  {
    return lexiLessthan(rest...);
  }
}

และ. cpp สำหรับกรณีฐานโดยไม่มีข้อโต้แย้ง:

bool lexiLessthan()
{
  return false;
}

ตอนนี้ตัวอย่างของคุณกลายเป็น:

return lexiLessthan(
    lhs.one_member, rhs.one_member, 
    lhs.another, rhs.another, 
    lhs.yet_more, rhs.yet_more
);

ฉันใส่วิธีแก้ปัญหาที่คล้ายกันที่นี่ แต่ไม่ต้องการตัวดำเนินการ! = stackoverflow.com/questions/11312448/…
steviekm3

3

ในความคิดของฉันคุณยังไม่ได้แก้ไขปัญหาเดียวกันกับวิธีแก้ปัญหาstd::tupleนั่นคือคุณต้องรู้ทั้งจำนวนและชื่อของตัวแปรสมาชิกแต่ละตัวคุณกำลังทำซ้ำสองครั้งในฟังก์ชัน คุณสามารถเลือกprivateรับมรดกได้

struct somestruct : private std::tuple<...> {
    T& GetSomeVariable() { ... }
    // etc
};

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


3
ฉันจะใช้ชื่อ accessors กับตัวแปรเช่นT& one_member(){ return std::get<0>(*this); }etc? แต่นั่นไม่จำเป็นต้องให้ฉันระบุวิธีการดังกล่าวสำหรับ "สมาชิก" แต่ละคนที่ฉันมีรวมถึงโอเวอร์โหลดสำหรับเวอร์ชัน const และ non-const หรือ
Xeo

@Xeo ฉันไม่เห็นว่า accessors ที่มีชื่อต้องการการทำงานมากกว่าการสร้างตัวแปรจริง ไม่ว่าจะด้วยวิธีใดคุณจะต้องมีชื่อแยกต่างหากสำหรับแต่ละตัวแปร ฉันคิดว่าจะมีการทำสำเนาสำหรับ const / non-const อย่างไรก็ตามคุณสามารถทำเทมเพลตทั้งหมดนี้ได้
Lee Louviere

1

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

คุณสามารถสร้าง accessors เพื่อ "เปลี่ยนชื่อ" สมาชิกของทูเพิล


ฉันอ่านคำถามของ OP ว่ามีความหมายว่า "กำลังใช้ชั้นเรียนของฉันoperator<โดยใช้std::tieเหตุผล? ฉันไม่เข้าใจว่าคำตอบนี้เกี่ยวข้องกับคำถามนั้นอย่างไร
ildjarn

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