Sử dụng siêu siêu thị trong C ++


203

Phong cách mã hóa của tôi bao gồm các thành ngữ sau:

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

      // Etc.
} ;

Điều này cho phép tôi sử dụng "siêu" làm bí danh cho Base, ví dụ, trong các hàm tạo:

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

Hoặc thậm chí khi gọi phương thức từ lớp cơ sở bên trong phiên bản bị ghi đè của nó:

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

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

Nó thậm chí có thể bị xiềng xích (mặc dù tôi vẫn phải tìm cách sử dụng cho việc đó):

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
}

Dù sao, tôi thấy việc sử dụng "typedef super" rất hữu ích, ví dụ, khi Base là dài dòng và / hoặc templated.

Thực tế là siêu được triển khai trong Java, cũng như trong C # (nơi nó được gọi là "cơ sở", trừ khi tôi sai). Nhưng C ++ thiếu từ khóa này.

Vì vậy, câu hỏi của tôi:

  • việc sử dụng typedef này có phổ biến / hiếm / chưa từng thấy trong mã bạn làm việc không?
  • Đây có phải là cách sử dụng typedef super Ok (tức là bạn thấy lý do mạnh mẽ hay không mạnh mẽ để không sử dụng nó)?
  • "siêu" nên là một điều tốt, nó nên được chuẩn hóa phần nào trong C ++, hay việc sử dụng này thông qua một typedef đã đủ chưa?

Chỉnh sửa: Roddy đã đề cập đến thực tế typedef nên ở chế độ riêng tư. Điều này có nghĩa là bất kỳ lớp dẫn xuất nào sẽ không thể sử dụng nó mà không khai báo lại. Nhưng tôi đoán nó cũng sẽ ngăn chặn siêu :: siêu xích (nhưng ai sẽ khóc vì điều đó?).

Chỉnh sửa 2: Bây giờ, vài tháng sau khi sử dụng ồ ạt "siêu", tôi hoàn toàn đồng ý với quan điểm của Roddy: "siêu" nên riêng tư. Tôi sẽ nâng cao câu trả lời của anh ấy hai lần, nhưng tôi đoán là không thể.


Tuyệt vời! Chính xác những gì tôi đang tìm kiếm. Đừng nghĩ rằng tôi đã từng cần sử dụng kỹ thuật này cho đến bây giờ. Giải pháp tuyệt vời cho mã đa nền tảng của tôi.
AlanKley

6
Đối với tôi có supervẻ như Javavà nó không có gì xấu, nhưng ... Nhưng C++hỗ trợ nhiều kế thừa.
ST3

2
@ user2623967: Phải. Trong trường hợp thừa kế đơn giản, một "siêu" là đủ. Bây giờ, nếu bạn có nhiều kế thừa, có "superA", "superB", v.v. là một giải pháp tốt: Bạn MUỐN gọi phương thức này từ cách thực hiện này hay cách khác, vì vậy bạn phải NÓI việc triển khai bạn muốn. Sử dụng typedef giống như "siêu" cho phép bạn cung cấp một tên có thể truy cập / ghi dễ dàng (thay vì nói, viết MyFirstBase<MyString, MyStruct<MyData, MyValue>>ở mọi nơi)
paercebal

Lưu ý rằng khi kế thừa từ một mẫu, bạn không cần bao gồm các đối số mẫu khi tham chiếu đến mẫu đó. Ví dụ: template <class baz> struct Foo {...void bar() {...} ...}; struct Foo2: Foo<AnnoyinglyLongListOfArguments> { void bar2() { ... Foo::bar(); ...} };Điều này làm việc với tôi với gcc 9.1 --std = c ++ 1y (c ++ 14).
Thanocation Papoutsidakis

1
... Ừm, sửa sai. Nó dường như hoạt động trong bất kỳ tiêu chuẩn c ++ nào, không chỉ 14.
Thanasis Papoutsidakis

Câu trả lời:


151

Bjarne Stroustrup đề cập đến Thiết kế và Tiến hóa của C ++ rằng supertừ khóa đã được ủy ban Tiêu chuẩn ISO C ++ xem xét lần đầu tiên C ++ được chuẩn hóa.

Dag Bruck đề xuất phần mở rộng này, gọi lớp cơ sở là "kế thừa". Đề xuất đề cập đến vấn đề đa thừa kế, và sẽ được gắn cờ sử dụng mơ hồ. Ngay cả Stroustrup cũng bị thuyết phục.

Sau khi thảo luận, Dag Bruck (vâng, cùng một người đưa ra đề xuất) đã viết rằng đề xuất này có thể thực hiện được, về mặt kỹ thuật và không có sai sót lớn và xử lý nhiều kế thừa. Mặt khác, không có tiếng nổ lớn, và ủy ban nên xử lý một vấn đề khó khăn hơn.

Michael Tiemann đến muộn, và sau đó cho thấy một siêu nhân được đánh máy sẽ hoạt động tốt, sử dụng kỹ thuật tương tự đã được hỏi về bài đăng này.

Vì vậy, không, điều này có lẽ sẽ không bao giờ được tiêu chuẩn hóa.

Nếu bạn không có bản sao, Thiết kế và Tiến hóa rất đáng giá. Bản sao được sử dụng có thể có khoảng $ 10.


5
D & E thực sự là một cuốn sách tốt. Nhưng có vẻ như tôi sẽ cần đọc lại - tôi cũng không nhớ câu chuyện nào.
Michael Burr

2
Tôi nhớ ba tính năng không được chấp nhận thảo luận trong D & E. Đây là lần đầu tiên (tra cứu "Michael Tiemann" trong chỉ mục để tìm câu chuyện), quy tắc hai tuần là lần thứ hai (tra cứu "quy tắc hai tuần" trong chỉ mục) và lần thứ ba được đặt tên là tham số (tra cứu " đối số được đặt tên "trong chỉ mục).
Max Lybbert

12
Có một lỗ hổng lớn trong typedefkỹ thuật: nó không tôn trọng DRY. Cách duy nhất là sử dụng các macro xấu xí để khai báo các lớp. Khi bạn kế thừa, cơ sở có thể là một lớp mẫu đa tham số dài hoặc tệ hơn. (ví dụ: nhiều lớp) bạn sẽ phải viết lại tất cả những điều đó lần thứ hai. Cuối cùng, tôi thấy một vấn đề lớn với các cơ sở mẫu có các đối số lớp mẫu. Trong trường hợp này, siêu là một mẫu (và không phải là bản in của mẫu). Mà không thể được đánh máy. Ngay cả trong C ++ 11 bạn cũng cần usingcho trường hợp này.
v.oddou

105

Tôi đã luôn sử dụng "kế thừa" chứ không phải siêu. (Có lẽ là do nền Delphi) và tôi luôn đặt nó ở chế độ riêng tư , để tránh sự cố khi 'kế thừa' bị bỏ sót trong một lớp nhưng một lớp con cố gắng sử dụng nó.

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

'Mẫu mã' tiêu chuẩn của tôi để tạo các lớp mới bao gồm typedef, vì vậy tôi có rất ít cơ hội để vô tình bỏ qua nó.

Tôi không nghĩ đề xuất "super :: super" bị xiềng xích là một ý tưởng hay - Nếu bạn đang làm điều đó, có lẽ bạn rất khó khăn trong một hệ thống phân cấp cụ thể và việc thay đổi nó có thể sẽ phá vỡ mọi thứ.


2
Đối với chuỗi siêu :: siêu, như tôi đã đề cập trong câu hỏi, tôi vẫn phải tìm một cách sử dụng thú vị cho điều đó. Hiện tại, tôi chỉ xem đó là một bản hack, nhưng nó đáng được đề cập, nếu chỉ vì sự khác biệt với Java (nơi bạn không thể xâu chuỗi "siêu").
paercebal

4
Sau vài tháng, tôi đã được chuyển đổi sang quan điểm của bạn (và tôi DID đã gặp lỗi vì một "siêu" bị lãng quên, như bạn đã đề cập ...). Bạn đoán khá đúng trong câu trả lời của bạn, bao gồm cả chuỗi, tôi đoán vậy. ^ _ ^ ...
paercebal

Làm thế nào để tôi sử dụng điều này bây giờ để gọi các phương thức lớp cha?
mLstudent33

điều đó có nghĩa là tôi phải khai báo tất cả các phương thức của lớp Cơ sở virtualnhư được hiển thị ở đây: martinbroadhurst.com/typedef-super.html
mLstudent33

36

Một vấn đề với điều này là nếu bạn quên (định nghĩa lại) siêu cho các lớp dẫn xuất, thì bất kỳ lệnh gọi nào đến super :: một cái gì đó sẽ biên dịch tốt nhưng có thể sẽ không gọi hàm mong muốn.

Ví dụ:

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()

        ...
    }
};

(Như Martin York đã chỉ ra trong các bình luận cho câu trả lời này, vấn đề này có thể được loại bỏ bằng cách đặt typedef riêng tư thay vì công khai hoặc được bảo vệ.)


Cảm ơn đã nhận xét. Tác dụng phụ này đã thoát khỏi thông báo của tôi. Mặc dù nó có thể sẽ không được biên dịch cho việc sử dụng hàm tạo, nhưng ở mọi nơi khác, tôi đoán, lỗi sẽ ở đó.
paercebal

5
Tuy nhiên, typedef riêng sẽ ngăn chặn việc sử dụng chuỗi, được đề cập trong bài viết gốc.
AnT

1
Chạy vào lỗi chính xác này là điều dẫn tôi đến câu hỏi này :(
Steve Vermeulen

Vì vậy, sử dụng super1cho Base và super2trong Derogen? DeriveAgain có thể sử dụng cả hai?
mLstudent33

20

FWIW Microsoft đã thêm tiện ích mở rộng cho __super trong trình biên dịch của họ.


Một vài nhà phát triển ở đây bắt đầu đẩy mạnh sử dụng __super. Lúc đầu, tôi đã đẩy lùi vì tôi cảm thấy đó là "sai" và "không phải là stanadard". TUY NHIÊN, tôi đã yêu thích nó.
Aardvark

8
Tôi đang làm việc trên một ứng dụng Windows và yêu thích tiện ích mở rộng __super. Thật buồn cho tôi rằng ủy ban tiêu chuẩn đã từ chối nó để ủng hộ thủ thuật typedef được đề cập ở đây, bởi vì trong khi thủ thuật typedef này tốt, nó yêu cầu bảo trì nhiều hơn một từ khóa trình biên dịch khi bạn thay đổi hệ thống phân cấp kế thừa và xử lý chính xác nhiều kế thừa (không yêu cầu hai kế thừa typedefs như super1 và super2). Nói tóm lại, tôi đồng ý với người bình luận khác rằng tiện ích mở rộng MS rất hữu ích và bất kỳ ai sử dụng Visual Studio chỉ nên cân nhắc sử dụng nó.
Brian

15

Siêu (hoặc được thừa kế) là Điều rất tốt vì nếu bạn cần dán một lớp thừa kế khác ở giữa Base và Derogen, bạn chỉ phải thay đổi hai điều: 1. "lớp Base: foo" và 2. typedef

Nếu tôi nhớ lại một cách chính xác, ủy ban Tiêu chuẩn C ++ đã xem xét thêm một từ khóa cho điều này ... cho đến khi Michael Tiemann chỉ ra rằng thủ thuật typedef này hoạt động.

Đối với nhiều kế thừa, vì nó nằm dưới sự kiểm soát của lập trình viên, bạn có thể làm bất cứ điều gì bạn muốn: có thể là super1 và super2, hoặc bất cứ điều gì.


13

Tôi chỉ tìm thấy một cách giải quyết khác. Tôi có một vấn đề lớn với cách tiếp cận typedef ngày hôm nay:

  • Typedef yêu cầu một bản sao chính xác của tên lớp. Nếu ai đó thay đổi tên lớp nhưng không thay đổi typedef thì bạn sẽ gặp vấn đề.

Vì vậy, tôi đã đưa ra một giải pháp tốt hơn bằng cách sử dụng một mẫu rất đơn giản.

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

Vì vậy, bây giờ, thay vì

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

bạn có

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

Trong trường hợp này, BaseAliaskhông riêng tư và tôi đã cố gắng bảo vệ chống lại việc sử dụng bất cẩn bằng cách chọn một loại tên sẽ cảnh báo cho các nhà phát triển khác.


4
publicbí danh là một nhược điểm, vì bạn cởi mở với lỗi được đề cập trong câu trả lời của RoddyKristopher (ví dụ: bạn có thể (do nhầm lẫn) xuất phát từ Derivedthay vì MakeAlias<Derived>)
Alexander Malakhov

3
Bạn cũng không có quyền truy cập vào các hàm tạo lớp cơ sở trong danh sách trình khởi tạo của mình. (Điều này có thể được bù trong C ++ 11 bằng cách sử dụng các hàm tạo kế thừa trong MakeAliashoặc chuyển tiếp hoàn hảo, nhưng nó yêu cầu bạn phải tham khảo các hàm MakeAlias<BaseAlias>tạo thay vì chỉ đề cập Ctrực tiếp đến các hàm tạo.)
Adam H. Peterson

12

Tôi không nhớ là đã thấy điều này trước đây, nhưng thoạt nhìn tôi thích nó. Như Ferruccio lưu ý, nó không hoạt động tốt khi đối mặt với MI, nhưng MI là ngoại lệ hơn so với quy tắc và không có gì nói rằng một cái gì đó cần phải có thể sử dụng ở mọi nơi để có ích.


6
Upvote chỉ cho cụm từ, "không có gì nói rằng một cái gì đó cần phải có thể sử dụng ở mọi nơi để có ích."
Tanktalus

1
Đã đồng ý. Nó rất hữu ích. Tôi chỉ nhìn vào thư viện đặc điểm loại boost để xem liệu có cách nào để tạo typedef thông qua một mẫu không. Thật không may, có vẻ như bạn không thể.
Ferruccio

9

Tôi đã thấy thành ngữ này được sử dụng trong nhiều mã và tôi khá chắc chắn rằng tôi thậm chí đã thấy nó ở đâu đó trong các thư viện của Boost. Tuy nhiên, theo như tôi nhớ thì tên phổ biến nhất là base(hoặc Base) thay vì super.

Thành ngữ này đặc biệt hữu ích nếu làm việc với các lớp mẫu. Ví dụ, xem xét lớp sau (từ một dự án thực tế ):

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;
    // …
}

Đừng bận tâm đến những cái tên ngộ nghĩnh. Điểm quan trọng ở đây là chuỗi thừa kế sử dụng các đối số kiểu để đạt được tính đa hình thời gian biên dịch. Thật không may, mức độ lồng nhau của các mẫu này trở nên khá cao. Do đó, viết tắt là rất quan trọng cho khả năng đọc và bảo trì.


Gần đây tôi đã sử dụng "siêu" vì lý do tương tự. Việc thừa kế công khai quá đau đớn để xử lý khác ... :-) ...
paercebal


4

việc sử dụng typedef này có phổ biến / hiếm / chưa từng thấy trong mã bạn làm việc không?

Tôi chưa bao giờ thấy mẫu đặc biệt này trong mã C ++ mà tôi làm việc cùng, nhưng điều đó không có nghĩa là nó không nằm ngoài đó.

Đây có phải là cách sử dụng typedef super Ok (tức là bạn thấy lý do mạnh mẽ hay không mạnh mẽ để không sử dụng nó)?

Nó không cho phép nhiều kế thừa (dù sao, sạch sẽ).

"siêu" nên là một điều tốt, nó nên được chuẩn hóa phần nào trong C ++, hay việc sử dụng này thông qua một typedef đã đủ chưa?

Đối với lý do được trích dẫn ở trên (nhiều kế thừa), không. Lý do tại sao bạn thấy "siêu" trong các ngôn ngữ khác mà bạn liệt kê là vì chúng chỉ hỗ trợ thừa kế duy nhất, vì vậy không có gì nhầm lẫn về "siêu" đang đề cập đến. Cấp, trong các ngôn ngữ đó, nó hữu ích nhưng nó không thực sự có chỗ trong mô hình dữ liệu C ++.

Ồ, và FYI: C ++ / CLI hỗ trợ khái niệm này dưới dạng từ khóa "__super". Tuy nhiên, xin lưu ý rằng C ++ / CLI cũng không hỗ trợ nhiều kế thừa.


4
Là một điểm đối lập, Perl có cả hai thừa kế SIÊU. Có một số nhầm lẫn, nhưng thuật toán mà VM đi qua để tìm thứ gì đó được ghi lại rõ ràng. Điều đó nói rằng, tôi hiếm khi thấy MI được sử dụng trong đó nhiều lớp cơ sở cung cấp các phương thức giống nhau trong đó có thể xảy ra nhầm lẫn.
Tanktalus

1
Microsoft Visual Studio triển khai từ khóa __super cho C ++ cho dù đó có phải là CLI hay không. Nếu chỉ một trong các lớp kế thừa đưa ra một phương thức chữ ký chính xác (trường hợp phổ biến nhất, như được đề cập bởi Tanktalus) thì nó chỉ chọn lựa chọn hợp lệ duy nhất. Nếu hai hoặc nhiều lớp kế thừa cung cấp một hàm khớp, nó không hoạt động và yêu cầu bạn phải rõ ràng.
Brian

3

Một lý do bổ sung để sử dụng typedef cho siêu lớp là khi bạn đang sử dụng các mẫu phức tạp trong kế thừa của đối tượng.

Ví dụ:

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

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

Trong lớp B, thật lý tưởng khi có một typedef cho A nếu không bạn sẽ bị mắc kẹt lặp lại nó ở mọi nơi bạn muốn tham khảo các thành viên của A.

Trong những trường hợp này, nó cũng có thể hoạt động với nhiều kế thừa, nhưng bạn sẽ không có một typedef có tên là 'super', nó sẽ được gọi là 'base_A_t' hoặc đại loại như thế.

--jeffk ++


2

Sau khi chuyển từ Turbo Pascal sang C ++ trở lại, tôi đã từng làm điều này để có một từ tương đương với từ khóa "kế thừa" Turbo Pascal, hoạt động theo cách tương tự. Tuy nhiên, sau khi lập trình trong C ++ được vài năm, tôi đã ngừng làm việc đó. Tôi thấy tôi không cần nó lắm.


1

Tôi không biết nó có hiếm hay không, nhưng tôi chắc chắn đã làm điều tương tự.

Như đã được chỉ ra, khó khăn trong việc tạo ra phần này của ngôn ngữ là khi một lớp sử dụng nhiều kế thừa.


1

Tôi sử dụng điều này theo thời gian. Chỉ khi tôi thấy mình gõ loại cơ sở một vài lần, tôi sẽ thay thế nó bằng một typedef tương tự như của bạn.

Tôi nghĩ rằng nó có thể là một sử dụng tốt. Như bạn nói, nếu lớp cơ sở của bạn là một mẫu, nó có thể lưu kiểu gõ. Ngoài ra, các lớp mẫu có thể lấy các đối số đóng vai trò là chính sách cho cách hoạt động của mẫu. Bạn có thể tự do thay đổi loại cơ sở mà không phải sửa tất cả các tham chiếu của mình với nó miễn là giao diện của cơ sở vẫn tương thích.

Tôi nghĩ rằng việc sử dụng thông qua typedef là đủ. Tôi không thể thấy nó sẽ được tích hợp vào ngôn ngữ như thế nào bởi vì nhiều kế thừa có nghĩa là có thể có nhiều lớp cơ sở, vì vậy bạn có thể gõ nó khi bạn thấy phù hợp với lớp mà bạn cảm thấy hợp lý là lớp cơ sở quan trọng nhất.


1

Tôi đã cố gắng giải quyết vấn đề chính xác này; Tôi đã đưa ra một vài ý tưởng, chẳng hạn như sử dụng các mẫu biến đổi và mở rộng gói để cho phép số lượng phụ huynh tùy ý, nhưng tôi nhận ra rằng điều đó sẽ dẫn đến việc thực hiện như 'super0' và 'super1'. Tôi đã bỏ nó đi vì điều đó sẽ hữu ích hơn nhiều so với việc không bắt đầu với nó.

Giải pháp của tôi liên quan đến một lớp người trợ giúp PrimaryParentvà được triển khai như vậy:

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

Sau đó, lớp nào bạn muốn sử dụng sẽ được khai báo như sau:

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

Để tránh sự cần thiết phải sử dụng kế thừa ảo PrimaryParenttrên BaseClass, một hàm tạo lấy một số lượng đối số biến đổi được sử dụng để cho phép xây dựng BaseClass.

Lý do đằng sau sự publickế thừa của BaseClassthành PrimaryParentlà để cho MyObjecttoàn quyền kiểm soát quyền thừa kế BaseClassmặc dù có một lớp người trợ giúp giữa họ.

Điều này không có nghĩa là mọi lớp bạn muốn superphải sử dụng PrimaryParentlớp người trợ giúp và mỗi đứa trẻ chỉ có thể thừa kế từ một lớp bằng cách sử dụng PrimaryParent(do đó là tên).

Một hạn chế khác cho phương thức này, chỉ MyObjectcó thể kế thừa một lớp kế thừa từ đó và lớp đó PrimaryParentphải được kế thừa bằng cách sử dụng PrimaryParent. Ý tôi là đây:

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>{}

Trước khi bạn loại bỏ điều này như một tùy chọn vì số lượng hạn chế dường như và thực tế có một lớp người trung gian giữa mỗi gia tài, những điều này không phải là xấu.

Đa kế thừa là một công cụ mạnh, nhưng trong hầu hết các trường hợp, sẽ chỉ có một cha mẹ chính và nếu có các cha mẹ khác, họ có thể sẽ là các lớp Mixin hoặc các lớp không được thừa kế từ mọi cách PrimaryParent. Nếu nhiều kế thừa vẫn cần thiết (mặc dù nhiều tình huống sẽ có lợi khi sử dụng thành phần để xác định một đối tượng thay vì thừa kế), hơn là chỉ xác định rõ ràng supertrong lớp đó và không kế thừa từ đó PrimaryParent.

Ý tưởng về việc phải định nghĩa supertrong mỗi lớp không hấp dẫn lắm đối với tôi, sử dụng PrimaryParentcho phép super, rõ ràng là một bí danh dựa trên kế thừa, để ở trong dòng định nghĩa lớp thay vì cơ thể lớp nơi dữ liệu sẽ đi.

Đó chỉ có thể là tôi.

Tất nhiên mọi tình huống đều khác nhau, nhưng hãy xem xét những điều tôi đã nói khi quyết định sử dụng tùy chọn nào.


1

Tôi sẽ không nói nhiều ngoại trừ mã hiện tại với các bình luận chứng minh rằng siêu không có nghĩa là gọi cơ sở!

super != base.

Nói tóm lại, "siêu" nghĩa là gì? và "cơ sở" nghĩa là gì?

  1. siêu phương tiện, gọi người thực hiện cuối cùng của một phương thức (không phải phương thức cơ sở)
  2. phương tiện cơ sở, chọn lớp nào là cơ sở mặc định trong nhiều kế thừa.

2 quy tắc này áp dụng cho các lớp typedefs.

Hãy xem xét người thực hiện thư viện và người sử dụng thư viện, ai là siêu và ai là cơ sở?

để biết thêm thông tin ở đây là mã làm việc để sao chép dán vào IDE của bạn:

#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();
}

Một điểm quan trọng khác ở đây là:

  1. cuộc gọi đa hình: cuộc gọi đi xuống
  2. siêu gọi: gọi lên

bên trong main()chúng tôi sử dụng các lệnh gọi đa hình mà siêu gọi lên trên, không thực sự hữu ích trong cuộc sống thực, nhưng nó cho thấy sự khác biệt.



0

Đây là phương pháp tôi sử dụng sử dụng macro thay vì typedef. Tôi biết rằng đây không phải là cách làm của C ++ nhưng nó có thể thuận tiện khi kết nối các trình vòng lặp với nhau thông qua kế thừa khi chỉ có lớp cơ sở xa nhất trong hệ thống phân cấp được thực hiện dựa trên phần bù được kế thừa.

Ví dụ:

// 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

Thiết lập chung mà tôi sử dụng làm cho nó rất dễ đọc và sao chép / dán giữa cây thừa kế có mã trùng lặp nhưng phải được ghi đè vì kiểu trả về phải khớp với lớp hiện tại.

Người ta có thể sử dụng chữ thường superđể sao chép hành vi nhìn thấy trong Java nhưng kiểu mã hóa của tôi là sử dụng tất cả các chữ cái viết hoa cho macro.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.