Điểm của một chức năng ảo thuần tư nhân là gì?


139

Tôi đã xem qua đoạn mã sau trong một tệp tiêu đề:

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

Đối với tôi, điều này ngụ ý rằng cả Enginelớp hoặc một lớp có nguồn gốc từ nó, phải cung cấp việc thực hiện cho các hàm ảo thuần túy đó. Nhưng tôi không nghĩ các lớp dẫn xuất có thể có quyền truy cập vào các hàm riêng đó để thực hiện lại chúng - vậy tại sao làm cho chúng ảo?

Câu trả lời:


209

Câu hỏi trong chủ đề cho thấy một sự nhầm lẫn khá phổ biến. Sự nhầm lẫn là đủ phổ biến, rằng Câu hỏi thường gặp về C ++ đã ủng hộ việc sử dụng ảo riêng trong một thời gian dài, bởi vì sự nhầm lẫn dường như là một điều xấu.

Vì vậy, để thoát khỏi sự nhầm lẫn trước tiên: Có, các hàm ảo riêng tư có thể được ghi đè trong các lớp dẫn xuất. Các phương thức của các lớp dẫn xuất không thể gọi các hàm ảo từ lớp cơ sở, nhưng chúng có thể cung cấp việc thực hiện riêng cho chúng. Theo Herb Sutter, việc có giao diện phi ảo công khai trong lớp cơ sở và triển khai riêng có thể được tùy chỉnh trong các lớp dẫn xuất, cho phép "tách biệt đặc tả giao diện khỏi đặc tả của hành vi tùy chỉnh của triển khai". Bạn có thể đọc thêm về nó trong bài viết "Ảo" của anh ấy .

Tuy nhiên, có một điều thú vị hơn trong mã bạn đã trình bày, theo tôi, đáng được chú ý hơn. Giao diện công cộng bao gồm một tập hợp các hàm không ảo quá tải và các hàm đó gọi các hàm ảo không công khai, không quá tải. Như thường lệ trong thế giới C ++, nó là một thành ngữ, nó có tên và tất nhiên nó rất hữu ích. Tên là (ngạc nhiên, ngạc nhiên!)

"Cuộc gọi không ảo quá tải công cộng được bảo vệ ảo không quá tải"

Nó giúp quản lý đúng quy tắc ẩn . Bạn có thể đọc thêm về nó ở đây , nhưng tôi sẽ cố gắng giải thích nó ngay.

Hãy tưởng tượng, các hàm ảo của Enginelớp cũng là giao diện của nó và nó là một tập hợp các hàm quá tải không phải là ảo thuần túy. Nếu chúng là ảo thuần, người ta vẫn có thể gặp phải vấn đề tương tự, như được mô tả dưới đây, nhưng thấp hơn trong hệ thống phân cấp lớp.

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

Bây giờ, giả sử bạn muốn tạo một lớp dẫn xuất và bạn chỉ cần cung cấp một triển khai mới cho phương thức, lấy hai số nguyên làm đối số.

class MyTurbochargedV8 : public Engine
{
public:
    // To prevent SetState( int var, bool val ) from the base class,
    // from being hidden by the new implementation of the other overload (below),
    // you have to put using declaration in the derived class
    using Engine::SetState;

    void SetState( int var, int val )  {/*new implementation*/}
};

Nếu bạn quên đặt khai báo sử dụng trong lớp dẫn xuất (hoặc để xác định lại tình trạng quá tải thứ hai), bạn có thể gặp rắc rối trong kịch bản dưới đây.

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

Nếu bạn không ngăn chặn sự che giấu của các Enginethành viên, tuyên bố:

myV8->SetState(5, true);

sẽ gọi void SetState( int var, int val )từ lớp dẫn xuất, chuyển đổi truesang int.

Nếu giao diện không phải là ảo và việc triển khai ảo không công khai, như trong sơ đồ của bạn, tác giả của lớp dẫn xuất có một vấn đề ít phải suy nghĩ và chỉ có thể viết

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};

Tại sao chức năng ảo phải riêng tư? Nó có thể được công khai?
Giàu

Tôi tự hỏi nếu các hướng dẫn được đưa ra bởi Herb Sutter trong bài viết "Ảo" của mình vẫn còn giữ đến ngày hôm nay?
Nurabha

@Rich Bạn có thể, nhưng bằng cách làm cho chúng không công khai, bạn có thể truyền đạt ý định của họ rõ ràng hơn. Đầu tiên, nó cho thấy một sự tách biệt các mối quan tâm nếu bạn gắn bó với việc làm cho giao diện công khai và việc triển khai không công khai. Thứ hai, nếu bạn muốn kế thừa các lớp để có thể gọi các triển khai cơ sở, bạn có thể khai báo chúng được bảo vệ; nếu bạn chỉ muốn họ cung cấp các triển khai của riêng họ mà không gọi vào các cơ sở, bạn làm cho chúng riêng tư.
Dan

43

Hàm ảo thuần túy là cơ sở của thành ngữ giao diện Không ảo (OK, nó không hoàn toàn luôn ảo thuần , nhưng vẫn ảo ở đó). Tất nhiên, điều này cũng được sử dụng cho những thứ khác, nhưng tôi thấy điều này hữu ích nhất (: Trong hai từ: trong một chức năng công cộng, bạn có thể đặt một số điều phổ biến (như ghi nhật ký, thống kê, v.v.) vào đầu và ở cuối hàm và sau đó, "ở giữa" để gọi hàm ảo riêng này, nó sẽ khác với lớp dẫn xuất cụ thể. Một cái gì đó như:

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

Ảo thuần túy - chỉ bắt buộc các lớp dẫn xuất phải thực hiện nó.

EDIT : Thông tin thêm về điều này: Wikipedia :: NVI-idiom


17

Vâng, đối với một, điều này sẽ cho phép một lớp dẫn xuất thực hiện một chức năng mà lớp cơ sở (chứa khai báo hàm ảo thuần túy) có thể gọi.


5
chỉ có lớp cơ sở có thể gọi!
gạch dưới

4

EDIT: Các tuyên bố được làm rõ về khả năng ghi đè và khả năng truy cập / gọi.

Nó sẽ có thể ghi đè các chức năng riêng tư đó. Ví dụ, ví dụ giả định sau đây hoạt động ( EDIT: đặt phương thức lớp dẫn xuất thành riêng tư và bỏ lời gọi phương thức lớp dẫn xuất main()để thể hiện rõ hơn ý định sử dụng mẫu thiết kế. ):

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

Private virtualcác phương thức trong một lớp cơ sở như các phương thức trong mã của bạn thường được sử dụng để triển khai mẫu thiết kế Phương thức mẫu . Mẫu thiết kế đó cho phép một người thay đổi hành vi của một thuật toán trong lớp cơ sở mà không thay đổi mã trong lớp cơ sở. Đoạn mã trên nơi các phương thức lớp cơ sở được gọi thông qua một con trỏ lớp cơ sở là một ví dụ đơn giản của mẫu Phương thức mẫu.


Tôi hiểu, nhưng nếu các lớp dẫn xuất có một loại truy cập nào, tại sao phải làm cho chúng riêng tư?
BeeBand

@BeeBand: Người dùng sẽ có quyền truy cập vào phương thức ảo lớp dẫn xuất công khai, nhưng sẽ không có quyền truy cập vào lớp cơ sở. Tác giả lớp dẫn xuất trong trường hợp này cũng có thể giữ phương thức ảo ghi đè riêng tư. Trong thực tế, tôi sẽ thực hiện thay đổi trong mã mẫu ở trên để nhấn mạnh điều đó. Dù bằng cách nào, họ luôn có thể kế thừa công khai và ghi đè các phương thức ảo của lớp cơ sở riêng, nhưng họ vẫn chỉ có quyền truy cập vào các phương thức ảo của lớp dẫn xuất của riêng họ. Lưu ý rằng tôi đang thực hiện phân biệt ghi đè beteween và truy cập / gọi.
Vô hiệu

bởi vì bạn đã sai khả năng hiển thị kế thừa giữa các lớp EngineDerivedEnginekhông liên quan gì đến những gì DerivedEnginecó thể hoặc không thể ghi đè (hoặc truy cập, đối với vấn đề đó).
wilmustell

@wilmustell: thở dài Bạn dĩ nhiên là đúng. Tôi sẽ cập nhật câu trả lời của tôi cho phù hợp.
Vô hiệu

3

Phương thức ảo riêng được sử dụng để giới hạn số lượng các lớp dẫn xuất có thể ghi đè hàm đã cho. Các lớp dẫn xuất phải ghi đè phương thức ảo riêng sẽ phải là bạn của lớp cơ sở.

Một lời giải thích ngắn gọn có thể được tìm thấy của DevX.com .


EDIT Một phương thức ảo riêng được sử dụng hiệu quả trong Mẫu Phương thức Mẫu . Các lớp dẫn xuất có thể ghi đè phương thức ảo riêng nhưng các lớp dẫn xuất không thể gọi nó là phương thức ảo riêng của lớp cơ sở (trong ví dụ của bạn SetStateBoolSetStateInt). Chỉ có lớp cơ sở mới có thể gọi phương thức ảo riêng của nó một cách hiệu quả ( Chỉ khi các lớp dẫn xuất cần gọi thực thi cơ sở của hàm ảo, làm cho hàm ảo được bảo vệ ).

Một bài viết thú vị có thể được tìm thấy về Virtuality .


2
@Gentman ... hmmm cuộn xuống bình luận của Colin D Bennett. Anh ta dường như nghĩ rằng "Một hàm ảo riêng có thể bị ghi đè bởi các lớp dẫn xuất, nhưng chỉ có thể được gọi từ bên trong lớp cơ sở." @Michael Goldshteyn cũng nghĩ như vậy.
BeeBand

Tôi đoán bạn đã quên nguyên tắc rằng một lớp dựa trên riêng tư không thể được nhìn thấy bởi lớp dẫn xuất của nó. Đó là quy tắc OOP và nó áp dụng cho tất cả các ngôn ngữ là OOP. Để một lớp dẫn xuất thực hiện phương thức ảo riêng của lớp cơ sở, nó phải là một friendlớp cơ sở. Qt đã thực hiện cùng một cách tiếp cận khi họ triển khai mô hình tài liệu XML DOM của họ.
Buhake Sindi

@Gentman: Không, tôi chưa quên. Tôi đã đánh máy trong bình luận của tôi. Thay vì "truy cập vào các phương thức của lớp cơ sở" tôi nên viết "có thể ghi đè các phương thức của lớp cơ sở". Lớp dẫn xuất chắc chắn có thể ghi đè phương thức lớp cơ sở ảo riêng ngay cả khi nó không thể truy cập phương thức lớp cơ sở đó. Bài viết DevX.com bạn chỉ ra là không chính xác (thừa kế công khai). Hãy thử mã trong câu trả lời của tôi. Mặc dù phương thức lớp cơ sở ảo riêng, lớp dẫn xuất có thể ghi đè lên nó. Chúng ta đừng nhầm lẫn khả năng ghi đè một phương thức lớp cơ sở ảo riêng với khả năng gọi nó.
Vô hiệu

@Gentman: @wilmustell đã chỉ ra lỗi trong câu trả lời / nhận xét của tôi. Yêu cầu của tôi về kế thừa ảnh hưởng đến khả năng truy cập lớp dẫn xuất của phương thức lớp cơ sở đã bị tắt. Tôi đã xóa bình luận xúc phạm đến câu trả lời của bạn.
Vô hiệu

@Void, tôi thấy rằng một lớp dẫn xuất có thể ghi đè phương thức ảo riêng của lớp cơ sở nhưng nó không thể sử dụng nó. Vì vậy, đây thực chất là một mẫu Phương thức mẫu.
Buhake Sindi

0

TL; DR trả lời:

Bạn có thể coi nó như một mức độ đóng gói khác - ở đâu đó giữa được bảo vệriêng tư : bạn không thể gọi nó từ lớp con, nhưng bạn có thể ghi đè lên nó.

Nó rất hữu ích khi thực hiện mẫu thiết kế Phương thức mẫu. Bạn có thể sử dụng được bảo vệ , nhưng riêng tư cùng với ảo có thể được coi là lựa chọn tốt hơn, vì đóng gói tốt hơn.

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.