Làm thế nào để gọi một hàm lớp cha từ hàm lớp dẫn xuất?


604

Làm thế nào để tôi gọi hàm cha từ một lớp dẫn xuất bằng C ++? Ví dụ, tôi có một lớp được gọi parentvà một lớp được gọi childlà bắt nguồn từ cha. Trong mỗi lớp có một printchức năng. Trong định nghĩa của chức năng in của trẻ em, tôi muốn thực hiện một cuộc gọi đến chức năng in của cha mẹ. Tôi sẽ đi đâu để tới đó?


Tất cả các giải pháp trên đều cho rằng chức năng in của bạn là một phương thức tĩnh. Đây có phải là trường hợp? Nếu phương thức không tĩnh thì các giải pháp trên không liên quan.
hhafez

14
hhafez, bạn đang nhầm cú pháp base :: function () trông giống như cú pháp gọi phương thức tĩnh nhưng nó hoạt động với các phương thức ví dụ trong ngữ cảnh này.
Motti

2
Tôi sẽ không sử dụng MSVC __super vì nó là nền tảng cụ thể. Mặc dù mã của bạn có thể không chạy trên bất kỳ nền tảng nào khác, tôi sẽ sử dụng các đề xuất khác vì chúng làm như ngôn ngữ dự định.
Teaser


Các antipotype nơi các lớp dẫn xuất luôn được yêu cầu để gọi các hàm của lớp cha là Gọi siêu
Rufus

Câu trả lời:


775

Tôi sẽ mạo hiểm nói rõ: Bạn gọi hàm, nếu nó được định nghĩa trong lớp cơ sở thì nó tự động có sẵn trong lớp dẫn xuất (trừ khi nó private).

Nếu có một hàm có cùng chữ ký trong lớp dẫn xuất, bạn có thể định hướng hàm đó bằng cách thêm tên của lớp cơ sở theo sau là hai dấu hai chấm base_class::foo(...). Bạn nên lưu ý rằng không giống như Java và C #, C ++ không có từ khóa cho "lớp cơ sở" ( superhoặc base) vì C ++ hỗ trợ nhiều kế thừa có thể dẫn đến sự mơ hồ.

class left {
public:
    void foo();
};

class right {
public:
    void foo();
};

class bottom : public left, public right {
public:
    void foo()
    {
        //base::foo();// ambiguous
        left::foo();
        right::foo();

        // and when foo() is not called for 'this':
        bottom b;
        b.left::foo();  // calls b.foo() from 'left'
        b.right::foo();  // call b.foo() from 'right'
    }
};

Ngẫu nhiên, bạn không thể xuất phát trực tiếp từ cùng một lớp hai lần vì sẽ không có cách nào để tham chiếu đến một trong các lớp cơ sở so với lớp kia.

class bottom : public left, public left { // Illegal
};

31
Tại sao bạn muốn thừa kế từ cùng một lớp hai lần?
Paul Brewczynski

65
@bluesm: trong OOP cổ điển, nó không có nhiều ý nghĩa, nhưng trong lập trình chung template<class A, class B> class C: public A, public B {};có thể có hai loại giống nhau vì lý do tùy thuộc vào cách sử dụng mã của bạn (làm cho A và B giống nhau), có thể là hai hoặc ba lớp trừu tượng cách từ một người không nhận thức được những gì bạn đã làm.
Emilio Garavaglia

8
Tôi nghĩ rằng nó hữu ích để thêm, rằng điều này sẽ gọi phương thức lớp cha ngay cả khi nó không được triển khai trực tiếp trong lớp cha, nhưng được triển khai trong một trong các lớp cha trong chuỗi thừa kế.
Maxim Lavrov

4
Trên một sidenote, nó làm tôi phát điên khi tôi cố gắng đưa nó vào một tập tin cpp. Tôi đã 'sử dụng không gian tên std'. 'Left' được định nghĩa ở đâu đó trong không gian tên đó. Ví dụ sẽ không được biên dịch - làm tôi phát điên :). Sau đó, tôi đã thay đổi 'trái' thành 'Trái'. Nhân tiện, ví dụ tuyệt vời.
Mathai

72
@Mathai Và đó là lý do tại sao bạn không nên sử dụng using namespace std.
JAB

193

Với lớp cha được đặt tên Parentvà lớp con được đặt tên Child, bạn có thể làm một cái gì đó như thế này:

class Parent {
public:
    virtual void print(int x);
}

class Child : public Parent {
    void print(int x) override;
}

void Parent::print(int x) {
    // some default behavior
}

void Child::print(int x) {
    // use Parent's print method; implicitly passes 'this' to Parent::print
    Parent::print(x);
}

Lưu ý rằng đó Parentlà tên thực tế của lớp và không phải là từ khóa.


Tất nhiên, điều này sẽ chỉ hữu ích nếu cuộc gọi cơ sở được xen kẽ với logic khác, nếu không sẽ không có điểm nào trong việc ghi đè hàm, vì vậy có lẽ nó hơi quá so với điểm;)
gạch dưới vào

1
@underscore_d thực sự, nó hữu ích ngay cả khi cuộc gọi cơ sở không được xen kẽ với logic khác. Giả sử lớp cha mẹ thực hiện mọi thứ bạn muốn, nhưng phơi bày một phương thức foo () bạn không muốn người dùng trẻ em sử dụng - vì foo () là vô nghĩa ở trẻ em hoặc người gọi bên ngoài đối với trẻ sẽ làm hỏng đứa trẻ là gì đang làm. Vì vậy, trẻ có thể sử dụng cha mẹ :: foo () trong một số trường hợp nhất định nhưng cung cấp việc triển khai foo để chúng ẩn foo () của cha mẹ khỏi bị gọi.
iheanyi

@iheanyi Nghe có vẻ thú vị, nhưng xin lỗi, tôi chưa nắm bắt được nó. Là foo()ở đây tương tự print()hoặc một chức năng riêng biệt? Và bạn có nghĩa là bằng cách sử dụng privatethừa kế để ẩn chi tiết thừa hưởng từ các cơ sở, và cung cấp các publicchức năng cho những thứ bạn shadowing làm muốn để lộ?
underscore_d

@underscore_d Có, foo()tương tự như print(). Hãy để tôi quay lại sử dụng print()vì tôi nghĩ nó sẽ có ý nghĩa hơn trong bối cảnh này. Giả sử ai đó đã tạo một lớp thực hiện một số thao tác trên một kiểu dữ liệu cụ thể, hiển thị một số bộ truy cập và có một print(obj&)phương thức. Tôi cần một lớp mới hoạt động array-of-objnhưng mọi thứ khác đều giống nhau. Thành phần kết quả trong rất nhiều mã trùng lặp. Kế thừa giảm thiểu điều đó, trong print(array-of-obj&) cuộc gọi vòng lặp print(obj&), nhưng không muốn khách hàng gọi print(obj&)vì không có ý nghĩa gì để họ làm như vậy
iheanyi

@underscore_d Điều này được khẳng định dựa trên giả định rằng tôi không thể cấu trúc lại các phần chung của lớp cha mẹ ban đầu hoặc làm như vậy là vô cùng tốn kém. Kế thừa tư nhân có thể hoạt động, nhưng sau đó bạn mất các bộ truy cập công cộng mà bạn đang dựa vào - và do đó, sẽ cần phải sao chép mã.
iheanyi

32

Nếu lớp cơ sở của bạn được gọi Basevà chức năng của bạn được gọi, FooBar()bạn có thể gọi nó trực tiếp bằng cách sử dụngBase::FooBar()

void Base::FooBar()
{
   printf("in Base\n");
}

void ChildOfBase::FooBar()
{
  Base::FooBar();
}

28

Trong MSVC có một từ khóa cụ thể của Microsoft cho điều đó: __super


MSDN: Cho phép bạn tuyên bố rõ ràng rằng bạn đang gọi một triển khai lớp cơ sở cho một chức năng mà bạn đang ghi đè.

// deriv_super.cpp
// compile with: /c
struct B1 {
   void mf(int) {}
};

struct B2 {
   void mf(short) {}

   void mf(char) {}
};

struct D : B1, B2 {
   void mf(short) {
      __super::mf(1);   // Calls B1::mf(int)
      __super::mf('s');   // Calls B2::mf(char)
   }
};


5
Eh, tôi thích typdefing cha mẹ như một cái gì đó như super.
Thomas Eding

26
Tôi sẽ không cố gắng biện minh cho việc sử dụng __super; Tôi đã đề cập ở đây như một đề nghị thay thế. Các nhà phát triển nên biết trình biên dịch của họ và hiểu những ưu và nhược điểm của các khả năng của nó.
Andrey

13
Tôi thà không khuyến khích bất cứ ai sử dụng nó, vì nó cản trở nghiêm trọng tính di động của mã.
Erbureth nói phục hồi Monica

26
Tôi không đồng ý với Andrey: Các nhà phát triển nên biết tiêu chuẩn và không cần bận tâm đến các tính năng của trình biên dịch, nếu chúng tôi xem xét việc viết phần mềm độc lập với trình biên dịch mà tôi nghĩ dù sao cũng là một ý tưởng tốt vì sớm hay muộn trong các dự án lớn, nhiều trình biên dịch là bất cứ cách nào được sử dụng.
Gabriel

7
"Các nhà phát triển nên biết trình biên dịch của họ" lý do này và việc bao gồm các tính năng không chuẩn, là điều dẫn đến IE6 ...
e2-e4

5

Nếu công cụ sửa đổi truy cập của chức năng thành viên lớp cơ sở được bảo vệ HOẶC công khai, bạn có thể thực hiện chức năng gọi thành viên của lớp cơ sở từ lớp dẫn xuất. Gọi hàm chức năng thành viên không ảo và ảo lớp cơ sở từ chức năng thành viên dẫn xuất có thể được thực hiện. Vui lòng tham khảo chương trình.

#include<iostream>
using namespace std;

class Parent
{
  protected:
    virtual void fun(int i)
    {
      cout<<"Parent::fun functionality write here"<<endl;
    }
    void fun1(int i)
    {
      cout<<"Parent::fun1 functionality write here"<<endl;
    }
    void fun2()
    {

      cout<<"Parent::fun3 functionality write here"<<endl;
    }

};

class Child:public Parent
{
  public:
    virtual void fun(int i)
    {
      cout<<"Child::fun partial functionality write here"<<endl;
      Parent::fun(++i);
      Parent::fun2();
    }
    void fun1(int i)
    {
      cout<<"Child::fun1 partial functionality write here"<<endl;
      Parent::fun1(++i);
    }

};
int main()
{
   Child d1;
   d1.fun(1);
   d1.fun1(2);
   return 0;
}

Đầu ra:

$ g++ base_function_call_from_derived.cpp
$ ./a.out 
Child::fun partial functionality write here
Parent::fun functionality write here
Parent::fun3 functionality write here
Child::fun1 partial functionality write here
Parent::fun1 functionality write here

1
Cảm ơn bạn đã mang một số ví dụ với virtual!
M.Ioan

5

Gọi phương thức cha với toán tử phân giải phạm vi cha.

Phụ huynh :: phương thức ()

class Primate {
public:
    void whatAmI(){
        cout << "I am of Primate order";
    }
};

class Human : public Primate{
public:
    void whatAmI(){
        cout << "I am of Human species";
    }
    void whatIsMyOrder(){
        Primate::whatAmI(); // <-- SCOPE RESOLUTION OPERATOR
    }
};

-15
struct a{
 int x;

 struct son{
  a* _parent;
  void test(){
   _parent->x=1; //success
  }
 }_son;

 }_a;

int main(){
 _a._son._parent=&_a;
 _a._son.test();
}

Ví dụ tham khảo.


2
Bạn có thể vui lòng chỉnh sửa trong một giải thích tại sao / làm thế nào mã này trả lời câu hỏi? Các câu trả lời chỉ dành cho mã không được khuyến khích, bởi vì chúng không dễ học như mã với một lời giải thích. Không có lời giải thích, sẽ mất nhiều thời gian và công sức hơn để hiểu những gì đã được thực hiện, những thay đổi được thực hiện đối với mã hoặc nếu mã này hữu ích. Lời giải thích rất quan trọng đối với những người cố gắng học hỏi từ câu trả lời và những người đánh giá câu trả lời để xem nó có hợp lệ hay không, có giá trị bỏ phiếu.
Makyen 23/2/2015

3
Câu trả lời này là về các lớp lồng nhau trong khi câu hỏi là về các lớp dẫn xuất (mặc dù các từ 'cha mẹ' và 'con' hơi sai chính tả) và do đó không trả lời câu hỏi nào cả.
Julian Matokic
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.