ใช้“ super” ใน C ++


203

รูปแบบการเข้ารหัสของฉันมีสำนวนดังต่อไปนี้

class Derived : public Base
{
   public :
      typedef Base super; // note that it could be hidden in
                          // protected/private section, instead

      // Etc.
} ;

สิ่งนี้ทำให้ฉันสามารถใช้ "super" เป็น alias to Base, ตัวอย่างเช่นใน constructors:

Derived(int i, int j)
   : super(i), J(j)
{
}

หรือแม้กระทั่งเมื่อเรียกใช้เมธอดจากคลาสฐานภายในเวอร์ชันที่แทนที่:

void Derived::foo()
{
   super::foo() ;

   // ... And then, do something else
}

มันสามารถถูกล่ามโซ่ (ฉันยังคงต้องค้นหาการใช้งานสำหรับสิ่งนั้น):

class DerivedDerived : public Derived
{
   public :
      typedef Derived super; // note that it could be hidden in
                             // protected/private section, instead

      // Etc.
} ;

void DerivedDerived::bar()
{
   super::bar() ; // will call Derived::bar
   super::super::bar ; // will call Base::bar

   // ... And then, do something else
}

อย่างไรก็ตามฉันพบว่าการใช้ "typedef super" มีประโยชน์มากเช่นเมื่อ Base เป็น verbose และ / หรือ templated

ความจริงก็คือ super ใช้งานใน Java เช่นเดียวกับใน C # (ที่เรียกว่า "ฐาน" เว้นแต่ฉันผิด) แต่ C ++ ขาดคำหลักนี้

ดังนั้นคำถามของฉัน:

  • การใช้ typedef super common / Rare / ไม่เคยเห็นในรหัสที่คุณใช้
  • การใช้ typedef super Ok นี้ (เช่นคุณเห็นเหตุผลที่แข็งแกร่งหรือไม่แรงเพื่อไม่ให้ใช้)
  • ควร "super" เป็นสิ่งที่ดีควรเป็นมาตรฐานค่อนข้างใน C ++ หรือการใช้งานผ่าน typedef เพียงพอหรือไม่

แก้ไข: Roddy พูดถึงข้อเท็จจริงที่ typedef ควรเป็นแบบส่วนตัว นี่หมายความว่าคลาสที่ได้รับมาจะไม่สามารถใช้งานได้โดยไม่ต้องประกาศใหม่ แต่ฉันเดาว่ามันจะป้องกัน super :: super chaining (แต่ใครจะร้องไห้เพื่อสิ่งนั้น?)

แก้ไข 2:ตอนนี้หลายเดือนหลังจากใช้ "super" อย่างหนาแน่นฉันเห็นด้วยกับมุมมองของ Roddy: "super" ควรเป็นส่วนตัว ฉันจะถอนคำตอบของเขาสองครั้ง แต่ฉันเดาไม่ได้


! น่ากลัว สิ่งที่ฉันกำลังมองหา อย่าคิดว่าฉันต้องใช้เทคนิคนี้มาก่อนจนกระทั่งตอนนี้ ทางออกที่ดีสำหรับรหัสข้ามแพลตฟอร์มของฉัน
AlanKley

6
สำหรับฉันsuperดูเหมือนว่าJavaและมันไม่มีอะไรเลวร้าย แต่ ... แต่C++รองรับมรดกหลาย
ST3

2
@ user2623967: ถูกต้อง ในกรณีของการสืบทอดง่าย ๆ หนึ่ง "super" ก็เพียงพอแล้ว ทีนี้ถ้าคุณมีหลายมรดกการมี "superA", "superB" ฯลฯ เป็นทางออกที่ดี: คุณต้องการที่จะเรียกวิธีการจากการใช้งานอย่างใดอย่างหนึ่งหรืออื่น ๆ ดังนั้นคุณต้องบอกสิ่งที่การดำเนินการที่คุณต้องการ การใช้ typedef ที่คล้ายกับ "super" ช่วยให้คุณสามารถระบุชื่อที่เข้าถึงได้ง่าย / เขียนได้ (แทนที่จะพูดเขียนMyFirstBase<MyString, MyStruct<MyData, MyValue>>ทุกที่)
paercebal

โปรดทราบว่าเมื่อสืบทอดจากเทมเพลตคุณไม่จำเป็นต้องรวมอาร์กิวเมนต์ของเทมเพลตเมื่ออ้างอิงถึง ตัวอย่างเช่น: template <class baz> struct Foo {...void bar() {...} ...}; struct Foo2: Foo<AnnoyinglyLongListOfArguments> { void bar2() { ... Foo::bar(); ...} };สิ่งนี้ใช้ได้กับฉันด้วย gcc 9.1 --std = c ++ 1y (c ++ 14)
Thanasis Papoutsidakis

1
... อืมการแก้ไข ดูเหมือนว่าจะทำงานในมาตรฐาน c ++ ใด ๆ ไม่ใช่แค่ 14
Thanasis Papoutsidakis

คำตอบ:


151

Bjarne Stroustrup กล่าวถึงในการออกแบบและวิวัฒนาการของ C ++ว่าsuperคำหลักนั้นได้รับการพิจารณาโดยคณะกรรมการมาตรฐาน ISO C ++ ในครั้งแรกที่ C + + เป็นมาตรฐาน

Dag Bruck เสนอส่วนขยายนี้เรียกคลาสพื้นฐาน "สืบทอด" ข้อเสนอดังกล่าวกล่าวถึงปัญหาการสืบทอดหลายรายการและอาจมีการใช้งานที่กำกวม แม้แต่ Stroustrup ก็ยังเชื่อมั่น

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

Michael Tiemann มาสายและจากนั้นแสดงให้เห็นว่า super typedef'ed จะทำงานได้ดีโดยใช้เทคนิคเดียวกับที่ถูกถามเกี่ยวกับในโพสต์นี้

ดังนั้นไม่อาจไม่เคยได้มาตรฐาน

หากคุณไม่มีสำเนาการออกแบบและวิวัฒนาการคุ้มค่ากับราคาที่คุ้มค่า สำเนาที่ใช้สามารถมีประมาณ $ 10


5
D & E เป็นหนังสือที่ดีแน่นอน แต่ดูเหมือนว่าฉันจะต้องอ่านใหม่ - ฉันจำไม่ได้ว่าเรื่องใดเรื่องหนึ่ง
Michael Burr

2
ฉันจำคุณสมบัติสามอย่างที่ไม่ได้รับการกล่าวถึงใน D & E นี่เป็นครั้งแรก (ค้นหา "Michael Tiemann" ในดัชนีเพื่อค้นหาเรื่องราว) กฎสองสัปดาห์คือวินาที (ค้นหา "กฎสองสัปดาห์" ในดัชนี) และอันดับที่สามคือพารามิเตอร์ (ค้นหา " อาร์กิวเมนต์ที่ระบุชื่อ "ในดัชนี)
Max Lybbert

12
มีข้อบกพร่องที่สำคัญในtypedefเทคนิคนี้: มันไม่เคารพ DRY วิธีเดียวที่จะใช้มาโครน่าเกลียดเพื่อประกาศคลาส เมื่อคุณสืบทอดฐานอาจเป็นคลาสเทมเพลตแบบหลายพารามิเตอร์ที่ยาวหรือแย่กว่านั้น (เช่นคลาสหลายคลาส) คุณจะต้องเขียนซ้ำทั้งหมดในครั้งที่สอง ในที่สุดฉันเห็นปัญหาใหญ่กับแม่แบบฐานที่มีอาร์กิวเมนต์คลาสแม่แบบ ในกรณีนี้ super จะเป็นเทมเพลต (ไม่ใช่ instanciation ของเทมเพลต) ซึ่งไม่สามารถพิมพ์ได้ แม้ใน C ++ 11 คุณจำเป็นusingสำหรับกรณีนี้
v.oddou

105

ฉันมักจะใช้ "สืบทอด" แทนที่จะดีกว่า (อาจเป็นเพราะพื้นหลัง Delphi) และฉันมักจะทำให้เป็นส่วนตัวเพื่อหลีกเลี่ยงปัญหาเมื่อ 'สืบทอด' ถูกละเว้นจากคลาสอย่างผิดพลาด แต่คลาสย่อยพยายามใช้งาน

class MyClass : public MyBase
{
private:  // Prevents erroneous use by other classes.
  typedef MyBase inherited;
...

'เทมเพลตรหัส' มาตรฐานของฉันสำหรับการสร้างคลาสใหม่รวมถึง typedef ดังนั้นฉันจึงมีโอกาสเพียงเล็กน้อยที่จะละเว้นมันโดยไม่ตั้งใจ

ฉันไม่คิดว่าคำแนะนำ "super :: super" ที่ถูกล่ามโซ่นั้นเป็นความคิดที่ดี - ถ้าคุณทำเช่นนั้นคุณอาจจะถูกผูกติดอยู่กับลำดับชั้นที่ยากมากและการเปลี่ยนมันอาจจะทำลายสิ่งที่ไม่ดีได้


2
สำหรับการผูกมัด super :: super อย่างที่ฉันพูดถึงในคำถามฉันยังต้องหาสิ่งที่น่าสนใจมาใช้ สำหรับตอนนี้ฉันเห็นว่ามันเป็นแค่แฮ็ค แต่มันก็คุ้มค่าที่จะกล่าวถึงหากความแตกต่างกับ Java (ซึ่งคุณไม่สามารถโยง "super") ได้
paercebal

4
หลังจากไม่กี่เดือนฉันก็เปลี่ยนเป็นมุมมองของคุณ (และฉันก็มีปัญหาเพราะ "ซูเปอร์" ที่ถูกลืมเหมือนที่คุณพูดถึง ... ) คุณคิดว่าคำตอบของคุณค่อนข้างถูกต้องรวมถึงการผูกมัดฉันคิดว่า ^ _ ^ ...
paercebal

ฉันจะใช้สิ่งนี้ในขณะนี้เพื่อเรียกวิธีการเรียนระดับผู้ปกครองได้อย่างไร
mLstudent33

หมายความว่าฉันต้องประกาศวิธีการเรียนพื้นฐานทั้งหมดvirtualดังที่แสดงที่นี่: martinbroadhurst.com/typedef-super.html
mLstudent33

36

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

ตัวอย่างเช่น:

class Base
{
public:  virtual void foo() { ... }
};

class Derived: public Base
{
public:
    typedef Base super;
    virtual void foo()
    {
        super::foo();   // call superclass implementation

        // do other stuff
        ...
    }
};

class DerivedAgain: public Derived
{
public:
    virtual void foo()
    {
        // Call superclass function
        super::foo();    // oops, calls Base::foo() rather than Derived::foo()

        ...
    }
};

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


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

5
อย่างไรก็ตาม typedef ส่วนตัวจะป้องกันการใช้งานที่ถูกล่ามโซ่กล่าวถึงในโพสต์ต้นฉบับ
AnT

1
การใช้งานข้อผิดพลาดที่แน่นอนนี้คือสิ่งที่นำฉันไปสู่คำถามนี้ :(
Steve Vermeulen

ใช้super1สำหรับ Base และsuper2In Derived? DerivedAgain สามารถใช้ทั้งสองได้หรือไม่
mLstudent33

20

FWIW Microsoft ได้เพิ่มส่วนขยายสำหรับ__superในคอมไพเลอร์


นักพัฒนาบางคนที่นี่เริ่มใช้งาน __super ตอนแรกฉันดันกลับเพราะฉันรู้สึกว่า "ผิด" และ "ไม่ใช่มาตรฐาน" อย่างไรก็ตามฉันรักมันมากขึ้นเรื่อย ๆ
Aardvark

8
ฉันทำงานบนแอพ windows และรักส่วนขยาย __super มันทำให้ฉันเศร้าใจที่คณะกรรมการมาตรฐานปฏิเสธมันในความโปรดปรานของเคล็ดลับ typedef ที่กล่าวถึงที่นี่เพราะในขณะที่เคล็ดลับ typedef นี้ดีต้องมีการบำรุงรักษามากกว่าคำหลักคอมไพเลอร์เมื่อคุณเปลี่ยนลำดับชั้นของการสืบทอดและจัดการมรดกหลายอย่างถูกต้อง typedefs เช่น super1 และ super2) กล่าวโดยสรุปฉันเห็นด้วยกับผู้ให้ความเห็นคนอื่นว่าส่วนขยายของ MS นั้นมีประโยชน์มากและทุกคนที่ใช้ Visual Studio นั้นควรพิจารณาใช้เป็นอย่างยิ่ง
Brian

15

Super (หรือสืบทอดมา) เป็นสิ่งที่ดีมากเพราะถ้าคุณต้องการติดเลเยอร์การสืบทอดอื่นในระหว่าง Base และ Derived คุณต้องเปลี่ยนสองสิ่งเท่านั้น: 1. "คลาสฐาน: foo" และ 2. typedef

หากฉันจำได้ถูกต้องคณะกรรมการมาตรฐาน C ++ กำลังพิจารณาเพิ่มคำหลักสำหรับ ... จนกว่า Michael Tiemann ชี้ให้เห็นว่าเคล็ดลับ typedef นี้ทำงานได้

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


13

ฉันเพิ่งพบวิธีแก้ปัญหาอื่น ฉันมีปัญหาใหญ่กับวิธี typedef ซึ่ง bit ฉันวันนี้:

  • typedef ต้องการสำเนาที่แน่นอนของชื่อคลาส หากมีคนเปลี่ยนชื่อคลาส แต่ไม่เปลี่ยน typedef แล้วคุณจะพบปัญหา

ดังนั้นฉันจึงคิดวิธีที่ดีกว่าโดยใช้เทมเพลตที่ง่ายมาก

template <class C>
struct MakeAlias : C
{ 
    typedef C BaseAlias;
};

ดังนั้นตอนนี้แทน

class Derived : public Base
{
private:
    typedef Base Super;
};

คุณมี

class Derived : public MakeAlias<Base>
{
    // Can refer to Base as BaseAlias here
};

ในกรณีBaseAliasนี้ไม่ได้เป็นส่วนตัวและฉันพยายามป้องกันการใช้งานด้วยความประมาทโดยเลือกชื่อประเภทที่ควรแจ้งเตือนผู้พัฒนารายอื่น


4
publicนามแฝงเป็นข้อเสียเนื่องจากคุณเปิดรับข้อผิดพลาดที่กล่าวถึงในคำตอบของ RoddyและKristopher (เช่นคุณสามารถ (โดยไม่ตั้งใจ) ได้มาจากDerivedแทนที่จะเป็นMakeAlias<Derived>)
Alexander Malakhov

3
คุณยังไม่มีสิทธิ์เข้าถึงตัวสร้างคลาสพื้นฐานในรายการ initializer ของคุณ (ซึ่งสามารถได้รับการชดเชยใน C ++ 11 ใช้ก่อสร้างสืบทอดในMakeAliasหรือส่งต่อที่สมบูรณ์แบบ แต่มันไม่ทำให้คุณต้องดูที่MakeAlias<BaseAlias>การก่อสร้างมากกว่าแค่หมายถึงC's ก่อสร้างโดยตรง.)
อดัมเอชปีเตอร์สัน

12

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


6
โหวตขึ้นเพียงเพื่อวลี "ไม่มีอะไรที่บอกว่าบางสิ่งบางอย่างจะต้องใช้งานได้ทุกที่จะเป็นประโยชน์"
Tanktalus

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

9

ฉันเห็นสำนวนนี้ใช้ในหลาย ๆ รหัสและฉันค่อนข้างแน่ใจว่าฉันเคยเห็นมันที่ไหนสักแห่งในห้องสมุดของ Boost อย่างไรก็ตามเท่าที่ฉันจำได้ชื่อที่พบบ่อยที่สุดคือbase(หรือBase ) superแทน

สำนวนนี้มีประโยชน์อย่างยิ่งหากทำงานกับคลาสเทมเพลต เป็นตัวอย่างพิจารณาคลาสต่อไปนี้ (จากโครงการจริง ):

template <typename TText, typename TSpec>
class Finder<Index<TText, PizzaChili<TSpec> >, PizzaChiliFinder>
    : public Finder<Index<TText, PizzaChili<TSpec> >, Default>
{
    typedef Finder<Index<TText, PizzaChili<TSpec> >, Default> TBase;
    // …
}

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


ฉันใช้คำว่า "super" เมื่อเร็ว ๆ นี้ด้วยเหตุผลเดียวกัน มรดกของประชาชนเจ็บปวดเกินไปที่จะจัดการ ... :-) ...
paercebal


4

การใช้ typedef super common / Rare / ไม่เคยเห็นในรหัสที่คุณใช้

ฉันไม่เคยเห็นรูปแบบเฉพาะนี้ในรหัส C ++ ที่ฉันทำงานด้วย แต่นั่นไม่ได้หมายความว่าไม่ได้อยู่ที่นั่น

การใช้ typedef super Ok นี้ (เช่นคุณเห็นเหตุผลที่แข็งแกร่งหรือไม่แรงเพื่อไม่ให้ใช้)

มันไม่อนุญาตให้มีการสืบทอดหลายรายการ (เรียบร้อยแล้ว)

ควร "super" เป็นสิ่งที่ดีควรเป็นมาตรฐานค่อนข้างใน C ++ หรือการใช้งานผ่าน typedef เพียงพอหรือไม่

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

โอ้และ FYI: C ++ / CLI สนับสนุนแนวคิดนี้ในรูปแบบของคำหลัก "__super" โปรดทราบว่า C ++ / CLI นั้นไม่รองรับการสืบทอดหลายรายการเช่นกัน


4
ในฐานะที่เป็นเคาน์เตอร์, Perl มีทั้งมรดกที่หลากหลายและ SUPER มีความสับสนอยู่บ้าง แต่อัลกอริทึมที่ VM ต้องดำเนินการเพื่อค้นหาบางสิ่งจะถูกบันทึกไว้อย่างชัดเจน ที่กล่าวว่าฉันไม่ค่อยเห็น MI ใช้ที่ชั้นฐานหลายเสนอวิธีการเดียวกันที่อาจเกิดความสับสนต่อไป
Tanktalus

1
Microsoft Visual Studio ใช้คำหลัก __super สำหรับ C ++ ไม่ว่าจะเป็นของ CLI หรือไม่ ถ้าหนึ่งในคลาสที่สืบทอดมานั้นมีวิธีการที่ถูกต้อง (กรณีที่พบบ่อยที่สุด, ตามที่กล่าวไว้โดย Tanktalus) ก็จะเลือกตัวเลือกที่ถูกต้องเท่านั้น หากคลาสที่สืบทอดสองคลาสขึ้นไปจัดให้มีการจับคู่ฟังก์ชันมันจะไม่ทำงานและคุณต้องมีความชัดเจน
Brian

3

เหตุผลเพิ่มเติมหนึ่งข้อในการใช้ typedef สำหรับ superclass คือเมื่อคุณใช้เท็มเพลตที่ซับซ้อนในการสืบทอดของวัตถุ

ตัวอย่างเช่น

template <typename T, size_t C, typename U>
class A
{ ... };

template <typename T>
class B : public A<T,99,T>
{ ... };

ในคลาส B มันจะเหมาะอย่างยิ่งถ้ามี typedef สำหรับ A ไม่เช่นนั้นคุณจะติดซ้ำทุกที่ที่คุณต้องการอ้างอิงสมาชิกของ A

ในกรณีเหล่านี้สามารถทำงานร่วมกับการสืบทอดหลายแบบได้เช่นกัน แต่คุณไม่มี typedef ชื่อ 'super' มันจะถูกเรียกว่า 'base_A_t' หรืออะไรทำนองนั้น

--jeffk ++


2

หลังจากโอนย้ายจาก Turbo Pascal เป็น C ++ ย้อนกลับไปในวันนั้นฉันเคยทำสิ่งนี้เพื่อที่จะได้คำหลัก Turbo Pascal ที่สืบทอดมาซึ่งเทียบเท่ากับคำหลัก อย่างไรก็ตามหลังจากการเขียนโปรแกรมใน C ++ ไม่กี่ปีฉันก็หยุดทำ ฉันพบว่าฉันไม่ต้องการมันมากนัก


1

ฉันไม่รู้ว่ามันหายากหรือเปล่า แต่ฉันก็ทำแบบเดียวกันอย่างแน่นอน

ดังที่ได้กล่าวไว้แล้วความยากลำบากในการสร้างส่วนนี้ของภาษาเองก็คือเมื่อชั้นเรียนใช้ประโยชน์จากการสืบทอดหลายอย่าง


1

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

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

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


1

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

โซลูชันของฉันเกี่ยวข้องกับคลาสผู้ช่วยPrimaryParentและมีการใช้งานดังนี้:

template<typename BaseClass>
class PrimaryParent : virtual public BaseClass
{
protected:
    using super = BaseClass;
public:
    template<typename ...ArgTypes>
    PrimaryParent<BaseClass>(ArgTypes... args) : BaseClass(args...){}
}

จากนั้นคลาสใดที่คุณต้องการใช้จะถูกประกาศเช่น:

class MyObject : public PrimaryParent<SomeBaseClass>
{
public:
    MyObject() : PrimaryParent<SomeBaseClass>(SomeParams) {}
}

เพื่อหลีกเลี่ยงการต้องใช้มรดกเสมือนPrimaryParentบนเป็นตัวสร้างการจำนวนตัวแปรของการขัดแย้งจะใช้ในการอนุญาตให้มีการก่อสร้างBaseClassBaseClass

เหตุผลที่อยู่เบื้องหลังการpublicสืบทอดBaseClassสู่PrimaryParentคือให้MyObjectมีการควบคุมอย่างเต็มที่ในการสืบทอดBaseClassแม้จะมีระดับผู้ช่วยระหว่างพวกเขา

นี่หมายความว่าทุกคลาสที่คุณต้องการจะsuperต้องใช้PrimaryParentคลาสผู้ช่วยและเด็กแต่ละคนสามารถสืบทอดจากคลาสหนึ่งโดยใช้เท่านั้นPrimaryParent(ดังนั้นชื่อ)

ข้อ จำกัด สำหรับวิธีการนี้ก็คือMyObjectสามารถสืบทอดเพียงหนึ่งชั้นซึ่งสืบทอดมาจากและที่หนึ่งจะต้องใช้จะได้รับมรดกPrimaryParent PrimaryParentนี่คือสิ่งที่ฉันหมายถึง:

class SomeOtherBase : public PrimaryParent<Ancestor>{}

class MixinClass {}

//Good
class BaseClass : public PrimaryParent<SomeOtherBase>, public MixinClass
{}


//Not Good (now 'super' is ambiguous)
class MyObject : public PrimaryParent<BaseClass>, public SomeOtherBase{}

//Also Not Good ('super' is again ambiguous)
class MyObject : public PrimaryParent<BaseClass>, public PrimaryParent<SomeOtherBase>{}

ก่อนที่คุณจะละทิ้งสิ่งนี้เป็นตัวเลือกเนื่องจากจำนวนข้อ จำกัด ที่ปรากฏและความจริงที่ว่ามีชนชั้นกลางระหว่างมรดกทุกสิ่งสิ่งเหล่านี้ไม่เลว

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

ความคิดของการกำหนดsuperในทุก ๆ ชั้นไม่น่าดึงดูดใจสำหรับฉันใช้การPrimaryParentอนุญาตให้ใช้superนามแฝงพื้นฐานที่สืบทอดได้อย่างชัดเจนเพื่ออยู่ในบรรทัดคำจำกัดความของชั้นแทนที่จะเป็นคลาสของร่างกายที่ข้อมูลควรไป

นั่นอาจเป็นฉัน

แน่นอนว่าทุกสถานการณ์แตกต่างกัน แต่พิจารณาสิ่งเหล่านี้ที่ฉันพูดเมื่อตัดสินใจเลือกตัวเลือกที่จะใช้


1

ฉันจะไม่พูดอะไรมากนอกจากโค้ดปัจจุบันที่มีความคิดเห็นที่แสดงว่า super ไม่ได้หมายถึงฐานการโทร!

super != base.

กล่าวโดยย่อคือ "super" ควรจะหมายถึงอะไรต่อไป? แล้ว "ฐาน" ควรจะหมายถึงอะไร?

  1. super หมายถึงการเรียกใช้ตัวสุดท้ายของ method (ไม่ใช่ method base)
  2. base หมายถึงการเลือกคลาสที่เป็นฐานเริ่มต้นในการสืบทอดหลายรายการ

กฎ 2 ข้อนี้ใช้กับประเภท typedef ของชั้นเรียน

พิจารณาตัวดำเนินการของไลบรารีและผู้ใช้ไลบรารีใครดีและเป็นฐาน

สำหรับข้อมูลเพิ่มเติมที่นี่คือรหัสการทำงานสำหรับการคัดลอกวางลงใน IDE ของคุณ:

#include <iostream>

// Library defiens 4 classes in typical library class hierarchy
class Abstract
{
public:
    virtual void f() = 0;
};

class LibraryBase1 :
    virtual public Abstract
{
public:
    void f() override
    {
        std::cout << "Base1" << std::endl;
    }
};

class LibraryBase2 :
    virtual public Abstract
{
public:
    void f() override
    {
        std::cout << "Base2" << std::endl;
    }
};

class LibraryDerivate :
    public LibraryBase1,
    public LibraryBase2
{
    // base is meaningfull only for this class,
    // this class decides who is my base in multiple inheritance
private:
    using base = LibraryBase1;

protected:
    // this is super! base is not super but base!
    using super = LibraryDerivate;

public:
    void f() override
    {
        std::cout << "I'm super not my Base" << std::endl;
        std::cout << "Calling my *default* base: " << std::endl;
        base::f();
    }
};

// Library user
struct UserBase :
    public LibraryDerivate
{
protected:
    // NOTE: If user overrides f() he must update who is super, in one class before base!
    using super = UserBase; // this typedef is needed only so that most derived version
    // is called, which calls next super in hierarchy.
    // it's not needed here, just saying how to chain "super" calls if needed

    // NOTE: User can't call base, base is a concept private to each class, super is not.
private:
    using base = LibraryDerivate; // example of typedefing base.

};

struct UserDerived :
    public UserBase
{
    // NOTE: to typedef who is super here we would need to specify full name
    // when calling super method, but in this sample is it's not needed.

    // Good super is called, example of good super is last implementor of f()
    // example of bad super is calling base (but which base??)
    void f() override
    {
        super::f();
    }
};

int main()
{
    UserDerived derived;
    // derived calls super implementation because that's what
    // "super" is supposed to mean! super != base
    derived.f();

    // Yes it work with polymorphism!
    Abstract* pUser = new LibraryDerivate;
    pUser->f();

    Abstract* pUserBase = new UserBase;
    pUserBase->f();
}

อีกจุดสำคัญที่นี่คือ:

  1. polymorphic call: เรียกลง
  2. super call: โทรขึ้น

ข้างในmain()เราใช้ polymorphic call downards ที่ super call up ไม่ได้มีประโยชน์ในชีวิตจริง แต่มันแสดงให้เห็นถึงความแตกต่าง



0

นี่เป็นวิธีที่ฉันใช้ซึ่งใช้มาโครแทน typedef ฉันรู้ว่านี่ไม่ใช่วิธี C ++ ในการทำสิ่งต่าง ๆ แต่มันจะสะดวกเมื่อผูกตัวทำซ้ำเข้าด้วยกันผ่านการสืบทอดเมื่อมีเพียงคลาสฐานที่อยู่ลึกลงไปตามลำดับชั้น

ตัวอย่างเช่น:

// some header.h

#define CLASS some_iterator
#define SUPER_CLASS some_const_iterator
#define SUPER static_cast<SUPER_CLASS&>(*this)

template<typename T>
class CLASS : SUPER_CLASS {
   typedef CLASS<T> class_type;

   class_type& operator++();
};

template<typename T>
typename CLASS<T>::class_type CLASS<T>::operator++(
   int)
{
   class_type copy = *this;

   // Macro
   ++SUPER;

   // vs

   // Typedef
   // super::operator++();

   return copy;
}

#undef CLASS
#undef SUPER_CLASS
#undef SUPER

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

หนึ่งสามารถใช้ตัวพิมพ์เล็กsuperเพื่อทำซ้ำพฤติกรรมที่เห็นใน Java แต่สไตล์การเขียนของฉันคือการใช้ตัวอักษรตัวพิมพ์ใหญ่ทั้งหมดสำหรับแมโคร

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