Tại sao các biến riêng được mô tả trong tệp tiêu đề có thể truy cập công khai?


11

OK, vì vậy hy vọng đây là một câu hỏi đủ chủ quan cho các lập trình viên, nhưng rồi đây. Tôi liên tục mở rộng kiến ​​thức về ngôn ngữ và thực hành kỹ thuật phần mềm ... và tôi đã gặp phải điều gì đó vô nghĩa với tôi.

Trong C ++, khai báo lớp bao gồm private:các phương thức và tham số trong tệp tiêu đề, theo lý thuyết, là những gì bạn chuyển cho người dùng để bao gồm nếu bạn biến chúng thành lib.

Trong Objective-C, @interfacechúng thực hiện khá nhiều thứ tương tự, buộc bạn phải liệt kê các thành viên riêng của mình (ít nhất, có một cách để có được các phương thức riêng tư trong tệp thực hiện).

Từ những gì tôi có thể nói, Java và C # cho phép bạn cung cấp giao diện / giao thức có thể khai báo tất cả các thuộc tính / phương thức có thể truy cập công khai và cung cấp cho bộ mã hóa khả năng ẩn tất cả các chi tiết triển khai trong tệp thực hiện.

Tại sao? Đóng gói là một trong những nguyên tắc chính của OOP, tại sao C ++ và Obj-C thiếu khả năng cơ bản này? Có một số cách thực hành tốt nhất xung quanh Obj-C hoặc C ++ che giấu tất cả việc thực hiện không?

Cảm ơn,


4
Tôi cảm nhận được nỗi đau của bạn. Tôi đã chạy đi la hét từ C ++ khi lần đầu tiên tôi thêm một trường riêng vào một lớp và phải biên dịch lại mọi thứ sử dụng nó.
Larry Coleman

@Larry, tôi không bận tâm lắm về nó, nhưng dường như nó được coi là một ngôn ngữ OO tuyệt vời, nhưng nó thậm chí không thể gói gọn "đúng cách".
Stephen Furlani

2
Đừng tin vào sự cường điệu. Có nhiều cách tốt hơn để làm OO, cả trong các trại gõ tĩnh và động.
Larry Coleman

Đó là một ngôn ngữ tuyệt vời theo một số cách, nhưng không phải vì khả năng OO của nó, đó là việc ghép Simula 67 lên C.
David Thornley

Bạn nên làm một số chương trình C. Sau đó, bạn sẽ hiểu rõ hơn về lý do tại sao các ngôn ngữ này là như vậy, bao gồm cả Java C #, v.v.
Henry

Câu trả lời:


6

Câu hỏi là liệu trình biên dịch có cần biết một đối tượng lớn như thế nào không. Nếu vậy, trình biên dịch phải biết về các thành viên riêng để đếm chúng.

Trong Java, có các kiểu và đối tượng nguyên thủy và tất cả các đối tượng được phân bổ riêng và các biến chứa chúng thực sự là các con trỏ. Do đó, vì một con trỏ là một đối tượng có kích thước cố định, trình biên dịch sẽ biết một biến đại diện cho một vật thể lớn đến mức nào mà không cần biết kích thước thực tế của đối tượng nhọn. Các constructor xử lý tất cả điều đó.

Trong C ++, có thể có các đối tượng được biểu diễn cục bộ hoặc trên heap. Do đó, trình biên dịch cần biết một đối tượng lớn như thế nào, để nó có thể phân bổ một biến cục bộ hoặc mảng.

Đôi khi, mong muốn phân chia chức năng lớp thành giao diện chung và mọi thứ riêng tư khác, và đó là nơi kỹ thuật PIMPL (Con trỏ để thực hiện) xuất hiện. Lớp sẽ có một con trỏ tới lớp triển khai riêng và các phương thức của lớp công khai có thể gọi cái đó.


trình biên dịch không thể tìm đến thư viện đối tượng để có được thông tin này? Tại sao thông tin này không thể đọc được bằng máy thay vì có thể đọc được?
Stephen Furlani

@Stephen: Tại thời điểm biên dịch, không nhất thiết phải có thư viện đối tượng. Vì kích thước của các đối tượng ảnh hưởng đến mã được biên dịch, nó phải được biết đến tại thời gian biên dịch, không chỉ ở thời gian liên kết. Hơn nữa, Ah có thể định nghĩa lớp A, Bh để xác định lớp B và các định nghĩa hàm trong A.cpp để sử dụng các đối tượng của lớp B và ngược lại. Trong trường hợp đó, cần phải biên dịch từng A.cpp và B.cpp trước cái kia, nếu lấy kích thước đối tượng từ mã đối tượng.
David Thornley

không thể liên kết được thực hiện trong quá trình biên dịch? Tôi thú nhận là không biết chi tiết ở độ sâu đó nhưng nếu giao diện của một đối tượng phơi bày nội dung của nó, những nội dung tương tự có thể bị lộ ra từ thư viện hoặc thành phần có thể đọc được bằng máy khác không? chúng phải được định nghĩa trong một tệp tiêu đề có thể truy cập công khai? Tôi hiểu đây là một phần của phần lớn các ngôn ngữ C, nhưng nó phải như vậy không?
Stephen Furlani

1
@Stephen: Liên kết chỉ có thể được thực hiện trong quá trình biên dịch nếu có tất cả các thành phần thích hợp. Ngay cả khi đó, nó sẽ là một quá trình phức tạp, bao gồm biên dịch một phần (mọi thứ trừ kích thước đối tượng), liên kết một phần (để có được kích thước đối tượng), sau đó biên dịch và liên kết cuối cùng. Cho rằng C và C ++ là những ngôn ngữ tương đối cũ, điều đó sẽ không xảy ra. Có lẽ ai đó có thể thiết kế một ngôn ngữ mới mà không cần tệp tiêu đề, nhưng nó sẽ không phải là C ++.
David Thornley

14

Do thiết kế của C ++, để tạo một đối tượng trên ngăn xếp, trình biên dịch phải biết nó lớn như thế nào. Để làm điều này, nó cần phải có tất cả các trường có trong tệp tiêu đề, vì đó là tất cả những gì trình biên dịch có thể nhìn thấy khi bao gồm tiêu đề.

Ví dụ, nếu bạn định nghĩa một lớp

class Foo {
    public int a;
    private int b;
};

sau đó sizeof(Foo)sizeof(a) + sizeof(b). Nếu có một số cơ chế để tách các trường riêng tư, thì tiêu đề có thể chứa

class Foo {
    public int a;
};

với sizeof(Foo) = sizeof(a) + ???.

Nếu bạn muốn thực sự ẩn dữ liệu riêng tư, hãy thử thành ngữ pimpl, với

class FooImpl;
class Foo {
    private FooImpl* impl;
}

trong tiêu đề và một định nghĩa FooImplchỉ trong Footệp thực hiện.


Oh. Tôi không biết đây là trường hợp. Có phải đó là lý do tại sao các ngôn ngữ như Java, C # và Obj-C có "Đối tượng lớp" để họ có thể hỏi lớp đó đối tượng nên lớn như thế nào không?
Stephen Furlani

4
Hãy thử sử dụng pimpl, một con trỏ để thực hiện riêng tư. Vì tất cả các con trỏ lớp có cùng kích thước, bạn không phải xác định lớp triển khai, chỉ khai báo nó.
Scott Wales

Tôi nghĩ rằng đó nên là một câu trả lời thay vì một bình luận.
Larry Coleman

1

Tất cả điều này đến với sự lựa chọn thiết kế.

Nếu bạn thực sự muốn ẩn các chi tiết triển khai riêng tư của lớp C ++ hoặc Objective-C, thì bạn có thể cung cấp một hoặc nhiều giao diện mà lớp hỗ trợ (lớp ảo thuần C ++, Objective-C @protatio) và / hoặc bạn tạo lớp có thể tự xây dựng bằng cách cung cấp một phương thức nhà máy tĩnh hoặc một đối tượng nhà máy lớp.

Lý do các biến riêng tư được hiển thị trong tệp tiêu đề / khai báo lớp / giao diện @ là vì người tiêu dùng của lớp của bạn có thể cần tạo một phiên bản mới của nó và một new MyClass()hoặc [[MyClass alloc]init]trong mã máy khách cần trình biên dịch để hiểu MyClass lớn như thế nào đối tượng là để làm phân bổ.

Java và C # cũng có các biến riêng của chúng được chi tiết trong lớp - chúng cũng không ngoại lệ với điều này, nhưng IMO mô hình giao diện phổ biến hơn nhiều với các ngôn ngữ đó. Bạn có thể không có mã nguồn trong từng trường hợp, nhưng có đủ siêu dữ liệu trong mã được biên dịch / byte để suy ra thông tin này. Vì C ++ và Objective-C không có siêu dữ liệu này, tùy chọn duy nhất là các chi tiết thực tế của giao diện lớp / @. Trong thế giới COM C ++, bạn không để lộ các biến riêng tư của bất kỳ lớp nào có thể cung cấp tệp tiêu đề - vì lớp này là thuần ảo. Một đối tượng nhà máy lớp được đăng ký để tạo các thể hiện thực tế và có một số siêu dữ liệu ở các dạng khác nhau.

Trong C ++ và Objective-C, việc xử lý tệp tiêu đề ít hơn so với việc viết và duy trì một tệp giao thức / giao thức @ bổ sung. Đây là một lý do mà bạn thấy việc triển khai riêng tư thường xuyên bị phơi bày.

Một lý do khác trong C ++ là các mẫu - trình biên dịch cần biết các chi tiết của lớp để tạo ra một phiên bản của lớp đó chuyên dùng cho các tham số được cung cấp. Kích thước của các thành viên của lớp sẽ thay đổi theo tham số hóa, vì vậy nó cần phải có thông tin này.

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.