Phương thức ảo riêng trong C ++


125

Ưu điểm của việc tạo private method trong C ++ là gì?

Tôi đã nhận thấy điều này trong một dự án C ++ mã nguồn mở:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};

9
Tôi nghĩ rằng câu hỏi là ngược. Lý do tạo ra một thứ gì đó ảo luôn giống nhau: để cho phép các lớp dẫn xuất ghi đè lên nó. Vì vậy, câu hỏi nên là: lợi thế của việc đặt một phương pháp ảo là riêng tư là gì? Câu trả lời là: đặt mọi thứ ở chế độ riêng tư theo mặc định. :-)
ShreevatsaR

1
@ShreevatsaR Nhưng bạn thậm chí còn không trả lời câu hỏi của chính mình ......
Spencer

@ShreevatsaR Tôi nghĩ bạn muốn nói ngược theo một cách khác: Lợi thế của việc tạo một phương pháp ảo không riêng tư là gì?
Peter - Khôi phục Monica

Câu trả lời:


116

Herb Sutter đã giải thích nó rất độc đáo ở đây .

Hướng dẫn # 2: Thích đặt các chức năng ảo ở chế độ riêng tư.

Điều này cho phép các lớp dẫn xuất ghi đè hàm để tùy chỉnh hành vi khi cần thiết, mà không làm lộ trực tiếp các hàm ảo bằng cách làm cho chúng có thể được gọi bởi các lớp dẫn xuất (càng tốt nếu các hàm chỉ được bảo vệ). Vấn đề là các chức năng ảo tồn tại để cho phép tùy chỉnh; trừ khi chúng cũng cần được gọi trực tiếp từ bên trong mã của các lớp dẫn xuất, không cần thiết phải đặt chúng ở chế độ riêng tư


Như bạn có thể đoán từ câu trả lời của tôi, tôi nghĩ rằng hướng dẫn số 3 của Sutter thay vì hướng dẫn số 2 ra khỏi cửa sổ.
Spencer

66

Nếu phương thức là ảo, nó có thể bị ghi đè bởi các lớp dẫn xuất, ngay cả khi nó là private. Khi phương thức ảo được gọi, phiên bản ghi đè sẽ được gọi.

(Phản đối Herb Sutter được Prasoon Saurav trích dẫn trong câu trả lời của mình, C ++ FAQ Lite khuyến cáo không nên sử dụng hình ảnh ảo riêng tư , chủ yếu là vì nó thường gây nhầm lẫn cho mọi người.)


41
Có vẻ như C ++ FAQ Lite kể từ đó đã thay đổi khuyến nghị của mình: " Câu hỏi thường gặp về C ++ trước đây được khuyến nghị sử dụng các hình ảnh ảo được bảo vệ hơn là các hình ảnh ảo riêng tư. Tuy nhiên, phương pháp tiếp cận ảo riêng tư hiện đã phổ biến đến mức khiến những người mới quen ít quan tâm hơn. "
Zack The Nhân

19
Tuy nhiên, sự nhầm lẫn của các chuyên gia vẫn là một mối quan tâm. Không ai trong số bốn chuyên gia C ++ ngồi cạnh tôi không biết về ảo riêng tư.
Newtonx

12

Bất chấp tất cả các lời kêu gọi tuyên bố một thành viên ảo là riêng tư, lập luận chỉ đơn giản là không có nước. Thông thường, việc ghi đè một hàm ảo của lớp dẫn xuất sẽ phải gọi phiên bản lớp cơ sở. Nó không thể nếu nó được khai báo private:

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

Bạn phải khai báo phương thức lớp cơ sởprotected .

Sau đó, bạn phải chấp nhận việc chỉ ra thông qua một nhận xét rằng phương thức nên được ghi đè nhưng không được gọi.

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

Vì vậy, hướng dẫn số 3 của Herb Sutter ... Nhưng dù sao con ngựa cũng đã ra khỏi chuồng.

Khi bạn khai báo điều gì đó, protectedbạn hoàn toàn tin tưởng người viết của bất kỳ lớp dẫn xuất nào hiểu và sử dụng đúng cách các phần tử bên trong được bảo vệ, giống như cách một friendkhai báo ngụ ý một sự tin cậy sâu sắc hơn choprivate các thành viên.

Người dùng có hành vi xấu do vi phạm sự tin tưởng đó (ví dụ như bị gắn nhãn 'không biết gì' do không thèm đọc tài liệu của bạn) chỉ có mình họ là người phải chịu trách nhiệm.

Cập nhật : Tôi đã có một số phản hồi tuyên bố rằng bạn có thể "chuỗi" các triển khai chức năng ảo theo cách này bằng cách sử dụng các chức năng ảo riêng tư. Nếu vậy, tôi chắc chắn muốn xem nó.

Các trình biên dịch C ++ mà tôi sử dụng chắc chắn sẽ không cho phép triển khai lớp dẫn xuất gọi một triển khai lớp cơ sở riêng.

Nếu ủy ban C ++ nới lỏng "riêng tư" để cho phép truy cập cụ thể này, tôi sẽ dành tất cả cho các chức năng ảo riêng tư. Như hiện tại, chúng tôi vẫn được khuyên là nên khóa cửa chuồng sau khi con ngựa bị đánh cắp.


3
Tôi thấy lập luận của bạn không hợp lệ. Bạn với tư cách là nhà phát triển API nên cố gắng tạo ra một giao diện khó sử dụng sai và không thiết lập một nhà phát triển khác vì những sai lầm của chính bạn khi làm như vậy. Những gì bạn muốn làm trong ví dụ của mình chỉ có thể được thực hiện với các phương thức ảo riêng tư.
sigy

1
Đó không phải là những gì tôi đã nói. Nhưng bạn có thể cấu trúc lại mã của mình để đạt được hiệu quả tương tự mà không cần phải gọi hàm lớp cơ sở riêng
sigy

3
Trong ví dụ của bạn, bạn muốn mở rộng hành vi của set_data. Các hướng dẫn m_data = ndata;cleanup();do đó có thể được coi là một bất biến phải giữ cho tất cả các triển khai. Do đó làm cho cleanup()không ảo và riêng tư. Thêm một cuộc gọi đến một phương thức riêng khác ảo và là điểm mở rộng của lớp của bạn. Bây giờ không cần các lớp dẫn xuất của bạn phải gọi các lớp cơ sở cleanup()nữa, mã của bạn vẫn sạch và giao diện của bạn khó sử dụng sai.
tiếc

2
@sigy Điều đó chỉ di chuyển các cột mục tiêu. Bạn cần phải nhìn xa hơn các ví dụ nhỏ. Khi có thêm các con cái khác cần gọi tất cả các cleanup()s trong chuỗi, đối số sẽ không còn nữa. Hay bạn đang đề xuất một chức năng ảo bổ sung cho mỗi con cháu trong chuỗi? Chà. Ngay cả Herb Sutter cũng cho phép các chức năng ảo được bảo vệ như một kẽ hở trong hướng dẫn # 3 của anh ấy. Dù sao, nếu không có một số mã thực tế, bạn sẽ không bao giờ thuyết phục được tôi.
Spencer

2
Sau đó, chúng ta hãy đồng ý không đồng ý;)
sigy

9

Lần đầu tiên tôi bắt gặp khái niệm này khi đọc 'C ++ hiệu quả' của Scott Meyers, Mục 35: Xem xét các lựa chọn thay thế cho các hàm ảo.Tôi muốn giới thiệu Scott Mayers cho những người khác có thể quan tâm.

Đó là một phần của Mô hình Phương pháp Mẫu thông qua thành ngữ Giao diện Không ảo : các phương thức đối mặt công khai không phải là ảo; thay vào đó, chúng bao bọc các cuộc gọi phương thức ảo là riêng tư. Lớp cơ sở sau đó có thể chạy logic trước và sau lệnh gọi hàm ảo riêng:

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

Tôi nghĩ rằng đây là một mẫu thiết kế rất thú vị và tôi chắc rằng bạn có thể thấy điều khiển được thêm vào hữu ích như thế nào.

  • Tại sao làm cho hàm ảo private? Lý do tốt nhất là chúng tôi đã cung cấppublic phương pháp đối mặt.
  • Tại sao không chỉ đơn giản là làm cho nó protectedđể tôi có thể sử dụng phương pháp này cho những điều thú vị khác? Tôi cho rằng nó sẽ luôn phụ thuộc vào thiết kế của bạn và cách bạn tin rằng lớp cơ sở phù hợp. Tôi cho rằng trình tạo lớp dẫn xuất nên tập trung vào việc triển khai logic cần thiết; mọi thứ khác đã được chăm sóc. Ngoài ra, có vấn đề về đóng gói.

Từ góc độ C ++, việc ghi đè một phương thức ảo private là hoàn toàn hợp pháp mặc dù bạn sẽ không thể gọi nó từ lớp của mình. Điều này hỗ trợ thiết kế được mô tả ở trên.


3

Tôi sử dụng chúng để cho phép các lớp dẫn xuất "điền vào chỗ trống" cho một lớp cơ sở mà không để lộ lỗ hổng như vậy cho người dùng cuối. Ví dụ: tôi có các đối tượng trạng thái cao bắt nguồn từ một cơ sở chung, chỉ có thể triển khai 2/3 tổng thể máy trạng thái (các lớp dẫn xuất cung cấp 1/3 còn lại tùy thuộc vào đối số mẫu và cơ sở không thể là khuôn mẫu cho lý do khác).

Tôi CẦN có lớp cơ sở chung để làm cho nhiều API công khai hoạt động chính xác (tôi đang sử dụng các mẫu khác nhau), nhưng tôi không thể để đối tượng đó ra ngoài tự nhiên. Tệ hơn nữa, nếu tôi để các miệng núi lửa trong máy trạng thái - dưới dạng các hàm ảo thuần túy - ở bất kỳ đâu ngoại trừ ở chế độ "Riêng tư", tôi cho phép một người dùng thông minh hoặc không biết gì xuất phát từ một trong các lớp con của nó ghi đè các phương thức mà người dùng không bao giờ được chạm vào. Vì vậy, tôi đặt 'bộ não' của máy trạng thái trong các chức năng ảo RIÊNG TƯ. Sau đó, các phần tử con trực tiếp của lớp cơ sở điền vào chỗ trống trên ghi đè KHÔNG ảo của chúng và người dùng có thể sử dụng các đối tượng kết quả một cách an toàn hoặc tạo các lớp dẫn xuất tiếp theo của riêng họ mà không phải lo lắng về việc làm rối máy trạng thái.

Đối với lập luận rằng bạn không nên có phương pháp ảo công khai, tôi nói rằng BS. Người dùng có thể ghi đè lên các ảo riêng tư một cách dễ dàng như công khai - họ đang định nghĩa các lớp mới. Nếu công chúng không nên sửa đổi một API nhất định, đừng làm cho nó ảo TẤT CẢ trong các đối tượng có thể truy cập công khai.

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.