typedefs ภายในใน C ++ - สไตล์ดีหรือสไตล์แย่?


179

บางสิ่งที่ฉันพบว่าตัวเองกำลังทำอยู่บ่อยครั้งเมื่อเร็ว ๆ นี้ได้ประกาศแนวความคิดที่เกี่ยวข้องกับชั้นเรียนเฉพาะภายในชั้นเรียนนั้นเช่น

class Lorem
{
    typedef boost::shared_ptr<Lorem> ptr;
    typedef std::vector<Lorem::ptr>  vector;

//
// ...
//
};

ประเภทเหล่านี้จะถูกใช้ที่อื่นในรหัส:

Lorem::vector lorems;
Lorem::ptr    lorem( new Lorem() );

lorems.push_back( lorem );

เหตุผลที่ฉันชอบ:

  • มันช่วยลดเสียงรบกวนที่นำโดยแม่ชั้นstd::vector<Lorem>จะกลายLorem::vectorเป็นต้น
  • มันทำหน้าที่เป็นคำสั่งของเจตนา - ในตัวอย่างข้างต้นคลาส Lorem มีจุดประสงค์ที่จะอ้างอิงนับผ่านboost::shared_ptrและเก็บไว้ในเวกเตอร์
  • จะช่วยให้การดำเนินการเปลี่ยนแปลง - เช่นถ้า Lorem จำเป็นต้องเปลี่ยนเป็นการอ้างอิงอย่างคร่าวๆนับ (ผ่านboost::intrusive_ptr) ในระยะต่อมานี่จะมีผลกระทบน้อยที่สุดกับรหัส
  • ฉันคิดว่ามันดูดีกว่าและอ่านง่ายกว่า

เหตุผลที่ฉันไม่ชอบ:

  • บางครั้งมีปัญหาเกี่ยวกับการพึ่งพา - หากคุณต้องการฝังพูดLorem::vectorในคลาสอื่น แต่ต้องการเพียง (หรือต้องการ) เพื่อส่งต่อประกาศ Lorem (ซึ่งตรงกันข้ามกับการแนะนำการพึ่งพาไฟล์ส่วนหัวของมัน) จากนั้นคุณต้องใช้ ประเภทที่ชัดเจน (เช่นboost::shared_ptr<Lorem>มากกว่าLorem::ptr) ซึ่งไม่สอดคล้องกันเล็กน้อย
  • มันอาจจะไม่ธรรมดามากและยากที่จะเข้าใจ?

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

คำตอบ:


153

ฉันคิดว่ามันเป็นสไตล์ที่ยอดเยี่ยมและฉันใช้มันด้วยตัวเอง เป็นการดีที่สุดที่จะ จำกัด ขอบเขตของชื่อให้มากที่สุดเท่าที่จะทำได้และการใช้คลาสเป็นวิธีที่ดีที่สุดในการทำ C ++ ตัวอย่างเช่นไลบรารี C ++ Standard ใช้งาน typedefs ภายในคลาสอย่างหนัก


นั่นเป็นประเด็นที่ดีฉันสงสัยว่ามันดู 'สวยกว่า' คือจิตใต้สำนึกของฉันชี้ให้เห็นว่าขอบเขตที่ จำกัด นั้นเป็นสิ่งที่ดี ฉันสงสัยว่าข้อเท็จจริงที่ STL ใช้ส่วนใหญ่ในเทมเพลตของชั้นทำให้มีการใช้งานที่แตกต่างกันหรือไม่? มันยากที่จะพิสูจน์ในชั้นเรียนที่เป็นรูปธรรม?
Will Baker

1
ไลบรารีมาตรฐานนั้นประกอบด้วยเทมเพลตมากกว่าคลาส แต่ฉันคิดว่าการให้เหตุผลนั้นเหมือนกันสำหรับทั้งคู่

14

มันทำหน้าที่เป็นคำสั่งของเจตนา - ในตัวอย่างข้างต้นคลาส Lorem มีวัตถุประสงค์เพื่อการอ้างอิงนับผ่าน boost :: shared_ptr และเก็บไว้ในเวกเตอร์

ตรงนี้เป็นสิ่งที่ไม่ได้ทำ

ถ้าฉันเห็น 'Foo :: Ptr' ในโค้ดฉันไม่รู้เลยว่ามันเป็น shared_ptr หรือ Foo * (STL มี :: ตัวพิมพ์ดีดตัวชี้ที่เป็น T * จดจำ) หรืออะไรก็ตาม Esp หากเป็นตัวชี้ที่ใช้ร่วมกันฉันจะไม่ให้ typedef เลย แต่ให้ shared_ptr ใช้อย่างชัดเจนในรหัส

ที่จริงแล้วฉันแทบจะไม่เคยใช้ typedefs นอก Template Metaprogramming

STL ทำสิ่งนี้ตลอดเวลา

การออกแบบ STL ที่มีแนวคิดที่กำหนดไว้ในแง่ของฟังก์ชั่นสมาชิกและ typedefs ที่ซ้อนกันเป็นประวัติศาสตร์ที่ผ่านมาห้องสมุดแม่แบบที่ทันสมัยใช้ฟังก์ชั่นฟรีและชั้นเรียนลักษณะ (cf. Boost.Graph) เพราะสิ่งเหล่านี้ไม่รวม การสร้างแบบจำลองแนวคิดและเนื่องจากทำให้การปรับประเภทที่ไม่ได้ออกแบบด้วยแนวคิดของไลบรารีแม่แบบที่กำหนดไว้ในใจง่ายขึ้น

อย่าใช้ STL เป็นเหตุผลในการทำผิดพลาดแบบเดียวกัน


ฉันเห็นด้วยกับส่วนแรกของคุณ แต่การแก้ไขล่าสุดของคุณเล็กน้อยสายตาสั้น ประเภทที่ซ้อนกันเช่นนี้ช่วยให้คำจำกัดความของคลาสลักษณะง่ายขึ้นเนื่องจากเป็นคลาสที่เหมาะสม พิจารณาใหม่std::allocator_traits<Alloc>ระดับ ... Allocคุณไม่จำเป็นต้องมีความเชี่ยวชาญมันสำหรับทุกจัดสรรเดียวที่คุณเขียนเพราะมันก็ยืมประเภทโดยตรงจาก
Dennis Zickefoose

@Dennis: ใน C ++ ความสะดวกสบายควรอยู่ที่ด้านข้างของ / user / ของไลบรารีไม่ใช่ด้านข้างของ / ผู้แต่ง /: ผู้ใช้ต้องการอินเทอร์เฟซแบบสม่ำเสมอสำหรับคุณลักษณะและมีเพียงคลาสลักษณะเท่านั้นที่สามารถให้ได้ เนื่องจากเหตุผลที่กล่าวมาข้างต้น) แต่ถึงแม้จะเป็นAllocนักเขียนก็ไม่ยากที่จะชำนาญstd::allocator_traits<>ในรูปแบบใหม่ของเขามากกว่าการเพิ่มประเภทที่ต้องการ ฉันได้แก้ไขคำตอบด้วยเนื่องจากคำตอบเต็มของฉันไม่พอดีกับความคิดเห็น
Marc Mutz - mmutz

แต่มันก็เป็นในด้านของผู้ใช้ ในฐานะที่เป็นผู้ใช้ของallocator_traitsความพยายามที่จะสร้างการจัดสรรที่กำหนดเองผมไม่ต้องกังวลกับสมาชิกสิบห้าของชนชั้นลักษณะ ... ทั้งหมดที่ฉันต้องทำคือการพูดtypedef Blah value_type;และให้ฟังก์ชั่นสมาชิกเหมาะสมและเริ่มต้นallocator_traitsที่จะคิดออก ส่วนที่เหลือ ดูตัวอย่าง Boost.Graph ของคุณ ใช่มันใช้ประโยชน์อย่างมากของคลาสลักษณะ ... แต่การใช้งานเริ่มต้นของการgraph_traits<G>สืบค้นแบบง่ายGสำหรับ typedef ภายในของมันเอง
Dennis Zickefoose

1
และแม้แต่ไลบรารีมาตรฐาน 03 ก็ใช้ประโยชน์จากคลาสของคุณลักษณะที่เหมาะสม ... ปรัชญาของไลบรารีไม่ได้ทำงานบนคอนเทนเนอร์ทั่วไป แต่ทำงานบนตัววนซ้ำ ดังนั้นจึงมีiterator_traitsคลาสเพื่อให้อัลกอริทึมทั่วไปของคุณสามารถสืบค้นข้อมูลที่เหมาะสมได้อย่างง่ายดาย ซึ่งอีกครั้งเริ่มต้นการสอบถามตัววนซ้ำสำหรับข้อมูลของตัวเอง ความยาวและระยะสั้นของมันคือลักษณะและชนิดของตัวพิมพ์ภายในนั้นไม่สามารถเกิดขึ้นพร้อมกันได้ ... พวกมันสนับสนุนซึ่งกันและกัน
Dennis Zickefoose

1
@Dennis: iterator_traitsกลายเป็นสิ่งจำเป็นเพราะT*ควรเป็นรูปแบบของRandomAccessIteratorแต่คุณไม่สามารถใส่ typedefs ที่ต้องการT*ได้ เมื่อเรามีiterator_traitsชนิดของข้อความที่ซ้อนกันกลายเป็นซ้ำซ้อนและฉันหวังว่าพวกเขาจะถูกลบออกไปแล้ว ด้วยเหตุผลเดียวกัน (เป็นไปไม่ได้ที่จะเพิ่ม typedefs ภายใน), T[N]ไม่ได้รูปแบบ STL Sequenceแนวคิดและคุณต้อง kludges std::array<T,N>เช่น Boost.Range แสดงให้เห็นว่าแนวคิดการเรียงลำดับที่ทันสมัยสามารถกำหนดT[N]รูปแบบได้อย่างไรเพราะมันไม่จำเป็นต้องพิมพ์แบบซ้อนหรือฟังก์ชั่นสมาชิก
Marc Mutz - mmutz


8

Typdefs เป็นสไตล์ที่ดีแน่นอน และ "เหตุผลที่ฉันชอบ" ของคุณทั้งหมดนั้นดีและถูกต้อง

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

คุณสามารถย้าย typedef นอกคลาส แต่ Class :: ptr สวยกว่ามากแล้ว ClassPtr ที่ฉันไม่ได้ทำ มันเหมือนกับเนมสเปซสำหรับฉัน - สิ่งต่าง ๆ เชื่อมต่อกันภายในขอบเขต

บางครั้งฉันก็ทำ

Trait<Loren>::ptr
Trait<Loren>::collection
Trait<Loren>::map

และมันสามารถเป็นค่าเริ่มต้นสำหรับทุกชั้นโดเมนและมีความเชี่ยวชาญบางอย่างสำหรับบางคน


3

STL ทำสิ่งนี้ตลอดเวลาประเภทของ typedefs เป็นส่วนหนึ่งของอินเตอร์เฟสสำหรับคลาสต่างๆใน STL

reference
iterator
size_type
value_type
etc...

เป็น typedefs ทั้งหมดที่เป็นส่วนหนึ่งของอินเตอร์เฟสสำหรับคลาสเทมเพลต STL ต่างๆ


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

3

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

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


3

ขณะนี้ฉันกำลังทำงานกับโค้ดซึ่งใช้ชนิดของคำศัพท์เหล่านี้อย่างเข้มข้น จนถึงตอนนี้ก็ดี

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

ดังนั้นก่อนที่จะใช้พวกเขามีบางจุดที่ต้องพิจารณา

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

1

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

เหตุผลที่คุณชอบมันยังคงตรงกันเนื่องจากพวกมันถูกแก้ไขโดยนามแฝงประเภทถึง typedef ไม่ใช่โดยการประกาศพวกมันในชั้นเรียนของคุณ


นั่นจะเป็นการสำรวจเนมสเปซที่ไม่ระบุชื่อด้วย typedefs ใช่มั้ย! ปัญหาของ typedef คือการซ่อนประเภทจริงซึ่งอาจทำให้เกิดข้อขัดแย้งเมื่อรวมอยู่ใน / โดยหลายโมดูลซึ่งยากต่อการค้นหา / แก้ไข เป็นการดีที่จะใส่สิ่งเหล่านี้ในเนมสเปซหรือในชั้นเรียน
Indy9000

3
ความขัดแย้งของชื่อและนโยบายเนมสเปซนิรนามไม่เกี่ยวข้องกับการรักษาชื่อในคลาสหรือนอก คุณสามารถมีชื่อที่ขัดแย้งกับชั้นเรียนของคุณไม่ได้กับแนวความคิดของคุณ ดังนั้นเพื่อหลีกเลี่ยงการตั้งชื่อให้ใช้เนมสเปซ ประกาศคลาสและประเภทที่เกี่ยวข้องในเนมสเปซของคุณ
Cătălin Pitiș

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

1

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

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