2012-04-27 62 views
5

Xét đoạn mã sau:Khi nào bạn nên hạn chế khả năng truy cập vào một chức năng ảo trong một lớp dẫn xuất?

class Base 
{ 
public: 
    virtual void Foo() {} 
}; 

class Derived : public Base 
{ 
private: 
    void Foo() {} 
}; 

void func() 
{ 
    Base* a = new Derived; 
    a->Foo(); //fine, calls Derived::Foo() 

    Derived* b = new Derived; 
// b->Foo(); //error 
    static_cast<Base*>(b)->Foo(); //fine, calls Derived::Foo() 
} 

Tôi đã nghe hai trường phái khác nhau về vấn đề này:

1) Để lại khả năng tiếp cận giống như các lớp cơ sở, vì người dùng có thể sử dụng để truy cập static_cast dù sao.

2) Đặt các chức năng ở chế độ riêng tư nhất có thể. Nếu người dùng yêu cầu a-> Foo() nhưng không phải b-> Foo(), thì Derived :: Foo phải là riêng tư. Nó luôn luôn có thể được công khai nếu và khi đó là cần thiết.

Có lý do nào để thích cái này hay cái kia không?

+1

Thiết kế này rất phản trực giác vì những lý do bạn đề cập. Tôi sẽ khuyên bạn nên chống lại nó trừ khi bạn gặp phải một kịch bản mà chỉ có thể được giải quyết theo cách này. –

+0

Nếu mục đích của bạn là hạn chế việc sử dụng _direct_ của lớp dẫn xuất (ví dụ: Mẫu nhà máy), thì bảo vệ hoặc riêng tư _inheritance_ là cách thích hợp hơn (thay vì hạn chế các phương thức cụ thể) – user396672

Trả lời

6

Giới hạn quyền truy cập vào thành viên trong một loại phụ vi phạm số Liskov substitution principle (số L trong SOLID). Tôi sẽ tư vấn chống lại nó nói chung.

Cập nhật: Nó có thể "làm việc", như trong mã biên dịch và chạy và tạo ra sản lượng dự kiến, nhưng nếu bạn đang ẩn thành viên ý định có lẽ làm cho kiểu phụ ít nói chung so với bản gốc. Đây là những gì phá vỡ nguyên tắc. Nếu thay vào đó, ý định của bạn là dọn dẹp giao diện kiểu phụ bằng cách chỉ để lại những gì thú vị cho người dùng API, hãy tiếp tục và làm điều đó.

+2

Tôi không nghĩ nguyên tắc thay thế này. Bạn vẫn có thể sử dụng một tham chiếu đến 'Derived' ở bất cứ nơi nào tham chiếu đến' Base' là bắt buộc, và nó sẽ hoạt động tốt. –

+0

@ BjörnPollex Tôi sẽ trả lời bằng bình luận nhưng sau đó nhận ra câu trả lời sẽ được hưởng lợi từ bản cập nhật. – Joni

+0

Mọi trình biên dịch có gắn cờ nó là cảnh báo không? (Chỉ cần một điểm dữ liệu khác) – Jon

1

Điều này tùy thuộc vào thiết kế của bạn, cho dù bạn có muốn truy cập chức năng virtual với đối tượng lớp dẫn xuất hay không.
Nếu không thì có, tốt hơn là đặt chúng private hoặc protected.

Không có sự khác biệt về thực thi mã dựa trên thông số truy cập, nhưng mã trở nên sạch hơn.
Sau khi bạn đã hạn chế quyền truy cập vào chức năng virtual của lớp học; người đọc của rằng class có thể chắc chắn rằng điều này sẽ không được gọi với bất kỳ đối tượng hoặc con trỏ của lớp dẫn xuất.

3

Không phải là câu trả lời cho câu hỏi ban đầu của bạn, nhưng nếu chúng ta đang nói về thiết kế lớp học ...

Như Alexandrescu và Sutter khuyên trong 39 thứ cai trị của họ, bạn nên thích sử dụng các chức năng không ảo công cộng và riêng tư/bảo vệ ảo:

class Base { 
public: 
    void Foo(); // fixed API function 

private: 
    virtual void FooImpl(); // implementation details 
}; 

class Derived { 
private: 
    virtual void FooImpl(); // special implementation 
}; 
+0

Bạn có thể hợp lý đặt việc triển khai 'Base :: Foo' ngay trong định nghĩa lớp,' void Foo() {FooImpl(); } '. Nó sẽ không bao giờ nhiều hơn một lớp lót có thể đoán trước được, đó là vấn đề. –

+0

@SteveJessop Đồng ý, nếu FooImpl() thực sự chỉ là một sự triển khai cho Foo. Trong thực tế, nó có thể là một phần của thuật toán Foo với cấu trúc cố định nhưng một số chi tiết nổi. Và đôi khi, các tiêu chuẩn mã hóa của công ty có thể cấm thực hiện trong khai báo lớp ngay cả đối với một lớp lót :) – anxieux

+1

@SteveJessop: Trên thực tế, KHÔNG! Vấn đề là bạn có thể thay đổi 'Foo' theo ý muốn bao gồm xử lý trước/sau trước khi gọi hàm' virtual'. Nó luôn luôn là một lớp lót, nó sẽ là khá vô nghĩa để bao gồm một wrapper thêm! –

Các vấn đề liên quan