รูปแบบสำหรับอินเทอร์เฟซที่ปลอดภัยใน C ++ คืออะไร


22

หมายเหตุ: ต่อไปนี้คือรหัส C ++ 03 แต่เราคาดว่าจะย้ายไปที่ C ++ 11 ในอีกสองปีข้างหน้าดังนั้นเราต้องจำไว้

ฉันกำลังเขียนแนวทาง (สำหรับมือใหม่และคนอื่น ๆ ) เกี่ยวกับวิธีเขียนอินเทอร์เฟซแบบนามธรรมใน C ++ ฉันได้อ่านบทความของซัทเทอร์ทั้งสองเรื่องแล้วค้นหาตัวอย่างและคำตอบจากอินเทอร์เน็ตและทำการทดสอบ

รหัสนี้จะต้องไม่รวบรวม!

void foo(SomeInterface & a, SomeInterface & b)
{
   SomeInterface c ;               // must not be default-constructible
   SomeInterface d(a);             // must not be copy-constructible
   a = b ;                         // must not be assignable
}

พฤติกรรมทั้งหมดข้างต้นค้นหาต้นตอของปัญหาของพวกเขาในการแบ่ง : อินเทอร์เฟซแบบนามธรรม (หรือคลาสที่ไม่เป็นใบไม้ในลำดับชั้น) ไม่ควรสร้างได้หรือไม่สามารถปรับเปลี่ยนได้

โซลูชันที่ 0: อินเทอร์เฟซพื้นฐาน

class VirtuallyDestructible
{
   public :
      virtual ~VirtuallyDestructible() {}
} ;

วิธีแก้ปัญหานี้เรียบง่ายและไร้เดียงสา: มันไม่ผ่านข้อ จำกัด ทั้งหมดของเรา: สามารถสร้างได้โดยเริ่มต้นคัดลอกสร้างและคัดลอกกำหนด (ฉันไม่แน่ใจเกี่ยวกับการย้ายตัวสร้างและการมอบหมาย แต่ฉันยังคงคิด 2 ปี มันออกมา)

  1. เราไม่สามารถประกาศ destructor pure virtual ได้เนื่องจากเราต้องการเก็บไว้ในบรรทัดและคอมไพเลอร์บางส่วนของเราจะไม่แยกย่อยวิธีเสมือนจริงด้วย body ที่ว่างเปล่าแบบอินไลน์
  2. ใช่จุดเดียวของคลาสนี้คือทำให้ผู้ใช้งานสามารถทำลายได้จริงซึ่งเป็นกรณีที่เกิดขึ้นได้ยาก
  3. แม้ว่าเราจะมีวิธีการเสมือนจริงเพิ่มเติม (ซึ่งเป็นกรณีส่วนใหญ่) คลาสนี้จะยังคงสามารถคัดลอกได้

ดังนั้นไม่ ...

วิธีแก้ปัญหาที่ 1: เพิ่ม :: noncopyable

class VirtuallyDestructible : boost::noncopyable
{
   public :
      virtual ~VirtuallyDestructible() {}
} ;

โซลูชันนี้ดีที่สุดเนื่องจากเป็นธรรมดาชัดเจนและ C ++ (ไม่มีมาโคร)

ปัญหาคือมันยังคงไม่ทำงานสำหรับอินเทอร์เฟซเฉพาะนั้นเนื่องจาก VirtuallyConstructible ยังสามารถสร้างเป็นค่าเริ่มต้นได้

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

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

สิ่งนี้ขัดกับ Rule of Zero ซึ่งเป็นตำแหน่งที่เราต้องการไป: หากการใช้งานเริ่มต้นนั้นใช้ได้เราก็ควรจะสามารถใช้มันได้

วิธีที่ 2: ทำให้พวกเขาได้รับการคุ้มครอง!

class MyInterface
{
   public :
      virtual ~MyInterface() {}

   protected :
      // With C++11, these methods would be "= default"
      MyInterface() {}
      MyInterface(const MyInterface & ) {}
      MyInterface & operator = (const MyInterface & ) { return *this ; }
} ;

รูปแบบนี้เป็นไปตามข้อ จำกัด ทางเทคนิคที่เรามี (อย่างน้อยในรหัสผู้ใช้): MyInterface ไม่สามารถสร้างเป็นค่าเริ่มต้นไม่สามารถคัดลอกสร้างและไม่สามารถคัดลอกกำหนดได้

นอกจากนี้ยังไม่มีการกำหนดข้อ จำกัด ในการใช้คลาสซึ่งเป็นอิสระจากการปฏิบัติตามกฎของศูนย์หรือประกาศตัวสร้าง / ตัวดำเนินการสองสามตัวเป็น "= default" ใน C ++ 11/14 โดยไม่มีปัญหา

ทีนี้นี่ค่อนข้างละเอียดและมีทางเลือกอื่นที่จะใช้มาโครเช่น:

class MyInterface
{
   public :
      virtual ~MyInterface() {}

   protected :
      DECLARE_AS_NON_SLICEABLE(MyInterface) ;
} ;

การป้องกันต้องอยู่นอกแมโคร (เนื่องจากไม่มีขอบเขต)

"namespaced" อย่างถูกต้อง (นั่นคือนำหน้าด้วยชื่อของ บริษัท หรือผลิตภัณฑ์ของคุณ) แมโครควรไม่เป็นอันตราย

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

ข้อสรุป

  • ฉันหวาดระแวงที่ต้องการให้รหัสป้องกันการแบ่งส่วนข้อมูลในส่วนต่อประสานหรือไม่? (ฉันเชื่อว่าฉันไม่ได้ แต่ไม่มีใครรู้ ... )
  • ทางออกที่ดีที่สุดในบรรดาข้างต้นคืออะไร?
  • มีอีกวิธีที่ดีกว่า

โปรดจำไว้ว่านี่เป็นรูปแบบที่จะใช้เป็นแนวทางสำหรับมือใหม่ (ท่ามกลางคนอื่น ๆ ) ดังนั้นวิธีแก้ปัญหาเช่น: "แต่ละกรณีควรมีการนำไปใช้" ไม่ใช่วิธีแก้ปัญหาที่ใช้งานได้

รางวัลและผลลัพธ์

ฉันให้รางวัลแก่coredumpเพราะใช้เวลาในการตอบคำถามและความเกี่ยวข้องของคำตอบ

วิธีแก้ปัญหาของฉันอาจจะเป็นเช่นนั้น:

class MyInterface
{
   DECLARE_CLASS_AS_INTERFACE(MyInterface) ;

   public :
      // the virtual methods
} ;

... ด้วยมาโครต่อไปนี้:

#define DECLARE_CLASS_AS_INTERFACE(ClassName)                                \
   public :                                                                  \
      virtual ~ClassName() {}                                                \
   protected :                                                               \
      ClassName() {}                                                         \
      ClassName(const ClassName & ) {}                                       \
      ClassName & operator = (const ClassName & ) { return *this ; }         \
   private :

นี่เป็นวิธีแก้ปัญหาที่ทำงานได้สำหรับปัญหาของฉันด้วยเหตุผลดังต่อไปนี้:

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

โปรดทราบว่าคำตอบอื่น ๆ ให้ข้อมูลเชิงลึกที่มีค่า ขอบคุณทุกคนที่ให้มันยิง

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


5
คุณไม่สามารถใช้ฟังก์ชั่นเสมือนบริสุทธิ์ในอินเทอร์เฟซได้หรือไม่ virtual void bar() = 0;ตัวอย่างเช่น? ซึ่งจะป้องกันไม่ให้อินเทอร์เฟซของคุณถูกห้ามใช้งาน
Morwenn

@ Morwenn: ดังที่กล่าวไว้ในคำถามที่จะแก้ปัญหา 99% ของกรณี (ฉันมุ่ง 100% ถ้าเป็นไปได้) แม้ว่าเราเลือกที่จะไม่สนใจ 1% ที่หายไป แต่ก็จะไม่สามารถแก้ไขการแบ่งส่วนที่ได้รับมอบหมาย ดังนั้นไม่ใช่นี่ไม่ใช่ทางออกที่ดี
paercebal

@ Morwenn: อย่างจริงจัง? ... :-D ... ฉันแรกเขียนคำถามนี้ใน StackOverflow แล้วเปลี่ยนใจก่อนที่จะส่งมัน คุณเชื่อว่าฉันควรลบที่นี่และส่งไปที่ SO หรือไม่
paercebal

ถ้าฉันถูกต้องทั้งหมดที่คุณต้องการคือvirtual ~VirtuallyDestructible() = 0และการสืบทอดคลาสอินเทอร์เฟซเสมือน (มีสมาชิกแบบนามธรรมเท่านั้น) คุณอาจละเว้นสิ่งที่ทำลายได้จริง
Dieter Lücking

5
@ paercebal: หากคอมไพเลอร์ chokes ในคลาสเสมือนจริงแล้วมันเป็นขยะ อินเทอร์เฟซที่แท้จริงคือโดยนิยามบริสุทธิ์เสมือน
ไม่มีใคร

คำตอบ:


13

วิธีที่เป็นที่ยอมรับในการสร้างส่วนต่อประสานใน C ++ คือการให้ destructor เสมือนแท้ สิ่งนี้รับรองว่า

  • ไม่สามารถสร้างอินสแตนซ์ของคลาสอินเตอร์เฟสได้เนื่องจาก C ++ ไม่อนุญาตให้คุณสร้างอินสแตนซ์ของคลาสนามธรรม สิ่งนี้จะดูแลข้อกำหนดที่ไม่สามารถสร้างขึ้นได้ (ทั้งค่าเริ่มต้นและสำเนา)
  • การเรียกdeleteตัวชี้ไปยังส่วนต่อประสานทำสิ่งที่ถูกต้อง: มันเรียก destructor ของคลาสที่ได้มามากที่สุดสำหรับอินสแตนซ์นั้น

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

คอมไพเลอร์ C ++ ใด ๆ ควรสามารถจัดการคลาส / ส่วนต่อประสานเช่นนี้ (ทั้งหมดในไฟล์ส่วนหัวเดียว):

class MyInterface {
public:
  virtual ~MyInterface() = 0;
protected:
  MyInterface& operator=(const MyInterface&) { return *this; } // or = default for C++14
};

inline MyInterface::~MyInterface() {}

หากคุณมีคอมไพเลอร์ที่ฉายาบนนี้ (ซึ่งหมายความว่าต้องเป็น pre-C ++ 98) ดังนั้นตัวเลือกที่ 2 (มีคอนสตรัคเตอร์ที่ได้รับการปกป้อง) นั้นดีที่สุดเป็นอันดับสอง

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


If you need [prevent assignment] to fail as well, then you must add a protected assignment operator to your interface.: นี่คือรากของปัญหาของฉัน กรณีที่ฉันต้องการอินเทอร์เฟซเพื่อสนับสนุนการมอบหมายต้องเป็นของหายากแน่นอน ในอีกกรณีหนึ่งกรณีที่ฉันต้องการผ่านส่วนต่อประสานโดยการอ้างอิง (กรณีที่เป็นโมฆะไม่เป็นที่ยอมรับ) และดังนั้นต้องการหลีกเลี่ยง no-op หรือ slicing ที่รวบรวมมากขึ้น
paercebal

เนื่องจากไม่ควรเรียกผู้ดำเนินการที่ได้รับมอบหมายทำไมคุณถึงให้คำจำกัดความ? กันทำไมไม่ทำมันprivate? นอกจากนี้คุณอาจต้องการจัดการกับ default- และ copy-ctor
Deduplicator

5

ฉันหวาดระแวง ...

  • ฉันหวาดระแวงที่ต้องการให้รหัสป้องกันการแบ่งส่วนข้อมูลในส่วนต่อประสานหรือไม่? (ฉันเชื่อว่าฉันไม่ได้ แต่ไม่มีใครรู้ ... )

นี่ไม่ใช่ปัญหาการจัดการความเสี่ยงใช่หรือไม่

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

ทางออกที่ดีที่สุด

  • ทางออกที่ดีที่สุดในบรรดาข้างต้นคืออะไร?

โซลูชันที่สองของคุณ ("ทำให้การป้องกัน") ดูดี แต่โปรดจำไว้ว่าฉันไม่ใช่ผู้เชี่ยวชาญ C ++
อย่างน้อยการใช้งานที่ไม่ถูกต้องดูเหมือนจะถูกรายงานอย่างผิดพลาดโดยคอมไพเลอร์ของฉัน (g ++)

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

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

  • หากใช้แล้วรูปแบบนั้นน่าจะใช้ได้และที่สำคัญกว่านั้นใช้อย่างถูกต้อง (เพียงตรวจสอบว่ามีprotectedคำหลัก)
  • หากไม่ได้ใช้คุณสามารถลองตรวจสอบสาเหตุที่มันไม่ได้

หากไม่มีมาโครคุณต้องตรวจสอบว่าจำเป็นต้องใช้รูปแบบหรือไม่และในทุกกรณี

ทางออกที่ดีกว่า

  • มีอีกวิธีที่ดีกว่า

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

ตัวอย่างเช่นชื่อเรื่องของแบบฝึกหัดอาจเป็น " รูปแบบของอินเทอร์เฟซที่ปลอดภัยใน C ++คืออะไร"

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

เกี่ยวกับคอมไพเลอร์

คุณพูด :

ฉันไม่มีอำนาจในการเลือกคอมไพเลอร์สำหรับผลิตภัณฑ์นี้

บ่อยครั้งที่ผู้คนมักจะพูดว่า"ฉันไม่มีสิทธิ์ทำ [X]" , "ฉันไม่ควรทำ [Y] ... " , ... เพราะพวกเขาคิดว่านี่เป็นไปไม่ได้และไม่ใช่เพราะพวกเขา ลองหรือถาม

มันอาจเป็นส่วนหนึ่งของรายละเอียดงานของคุณเพื่อให้ความเห็นเกี่ยวกับปัญหาทางเทคนิค หากคุณคิดว่าคอมไพเลอร์เป็นตัวเลือกที่สมบูรณ์แบบ (หรือไม่ซ้ำ) สำหรับโดเมนปัญหาของคุณให้ใช้มัน แต่คุณยังบอกด้วยว่า"destructors เสมือนจริงที่บริสุทธิ์พร้อมการติดตั้งแบบ inline ไม่ใช่จุดสำลักที่แย่ที่สุดที่ฉันเคยเห็น" ; จากความเข้าใจของฉันคอมไพเลอร์มีความพิเศษที่แม้แต่ผู้รู้ C ++ ที่มีความรู้ก็ยังมีปัญหาในการใช้งาน: ผู้เรียบเรียงดั้งเดิม / เจ้าของบ้านของคุณเป็นหนี้ทางเทคนิคและคุณมีสิทธิ์ (หน้าที่?) เพื่อหารือเกี่ยวกับปัญหา .

ลองประเมินค่าใช้จ่ายในการรักษาคอมไพเลอร์กับต้นทุนการใช้อีกอันหนึ่ง:

  1. คอมไพเลอร์ปัจจุบันทำให้อะไรที่คุณไม่มีใครสามารถทำได้
  2. รหัสผลิตภัณฑ์ของคุณสามารถคอมไพล์ได้อย่างง่ายดายโดยใช้คอมไพเลอร์อื่นหรือไม่? ทำไมไม่ ?

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


Am I paranoid...: "ทำให้อินเทอร์เฟซของคุณใช้งานง่ายถูกต้องและใช้งานไม่ถูกต้อง" ฉันได้ลิ้มรสหลักการเฉพาะนั้นเมื่อมีคนรายงานว่าวิธีการคงที่ของฉันถูกใช้อย่างไม่ถูกต้อง ข้อผิดพลาดที่เกิดขึ้นนั้นดูเหมือนไม่เกี่ยวข้องกันและใช้เวลาหลายชั่วโมงในการค้นหาวิศวกร "ข้อผิดพลาดของอินเทอร์เฟซ" นี้มีค่าเทียบเท่ากับการกำหนดการอ้างอิงอินเทอร์เฟซให้กับผู้อื่น ใช่ฉันต้องการหลีกเลี่ยงข้อผิดพลาดชนิดนั้น ยิ่งไปกว่านั้นใน C ++ ปรัชญาคือการจับมากที่สุดเท่าที่จะเป็นไปได้ในการรวบรวมและภาษาให้พลังนั้นแก่เราดังนั้นเราจึงไปกับมัน
paercebal

Best solution: ฉันเห็นด้วย. . . Better solution: นั่นเป็นคำตอบที่ยอดเยี่ยม ฉันจะทำงานกับมัน ... ตอนนี้เกี่ยวกับPure virtual classes: นี่คืออะไร? อินเตอร์เฟซนามธรรม C ++? (คลาสที่ไม่มีรัฐและวิธีการเสมือนบริสุทธิ์เท่านั้น?) วิธี "คลาสเสมือนจริง" นี้ป้องกันฉันจากการแบ่งส่วนข้อมูลได้อย่างไร (วิธีเสมือนจริงจะทำให้การสร้างอินสแตนซ์ไม่ได้คอมไพล์ แต่การทำสำเนาจะมอบหมายและย้ายการมอบหมายก็เช่นกัน IIRC)
paercebal

About the compiler: เราเห็นด้วย แต่คอมไพเลอร์ของเราอยู่นอกขอบเขตความรับผิดชอบของฉัน (ไม่ใช่ว่ามันจะขัดขวางไม่ให้ฉันแสดงความคิดเห็นต่อ ... :-p ... ) ฉันจะไม่เปิดเผยรายละเอียด (ฉันหวังว่าจะทำได้) แต่มันเชื่อมโยงกับเหตุผลภายใน (เช่นชุดทดสอบ) และเหตุผลภายนอก (เช่นลูกค้าเชื่อมโยงกับห้องสมุดของเรา) ในที่สุดการเปลี่ยนเวอร์ชั่นคอมไพเลอร์ (หรือแม้แต่การแพ็ตช์มัน) ไม่ใช่การดำเนินการเล็กน้อย ปล่อยให้คนเดียวแทนที่หนึ่งคอมไพเลอร์ที่ขาดด้วย gcc ล่าสุด
paercebal

@ paercebal ขอบคุณสำหรับความคิดเห็นของคุณ; เกี่ยวกับคลาสเสมือนจริงคุณพูดถูกมันไม่ได้แก้ไขข้อ จำกัด ทั้งหมดของคุณ (ฉันจะลบส่วนนี้) ฉันเข้าใจส่วน "ข้อผิดพลาดของอินเทอร์เฟซ" และการจับข้อผิดพลาดที่รวบรวมเวลามีประโยชน์อย่างไร แต่คุณถามว่าคุณหวาดระแวงหรือเปล่า โชคดีกับสิ่งที่คอมไพเลอร์ :)
coredump

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

2

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

วิธีที่ดีที่สุดที่ฉันรู้เพื่อหลีกเลี่ยงปัญหาเหล่านี้ใน C ++ คือการใช้ประเภทการลบออก นี่คือเทคนิคที่คุณซ่อนอินเทอร์เฟซ polymorphic ขณะใช้งานอยู่ด้านหลังอินเทอร์เฟซคลาสปกติ อินเทอร์เฟซคลาสปกตินี้มีความหมายตามตัวอักษรและดูแล polymorphic 'mess' ทั้งหมดที่อยู่ด้านหลังหน้าจอ std::functionเป็นตัวอย่างสำคัญของการลบประเภท

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

การสืบทอดเป็นฐานชั้นของความชั่วร้าย (เวอร์ชั่นสั้น)

Value Semantics และ Concepts-based Polymorphism (รุ่นยาว; ง่ายต่อการติดตาม แต่เสียงไม่ดี)


0

คุณไม่หวาดระแวง งานอาชีพแรกของฉันในฐานะโปรแกรมเมอร์ C ++ ส่งผลให้การแบ่งและการหยุดทำงาน ฉันรู้จักผู้อื่น ไม่มีวิธีแก้ปัญหาที่ดีสำหรับเรื่องนี้

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

หากโปรแกรมเมอร์ของคุณใช้บรรทัดคำสั่งให้ใช้ภาษาสคริปต์ใดก็ได้ที่คุณสามารถสร้างสคริปต์ NewMyCompanyInterface เพื่อสร้างรหัส

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

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

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