2012-10-10 21 views
5

Trong cuốn sách Ngôn ngữ lập trình C++, bởi Bjarne Stroustrup, tác giả giới thiệu một ma trận lớp mà phải triển khai hàm inv(). Trong phần 11.5.1, ông nói về hai khả năng làm điều đó. Một là để làm cho một chức năng thành viên và khác là để làm cho một chức năng người bạn inv(). Sau đó, đến cuối phần 11.5.2, nơi ông nói về làm cho việc lựa chọn có sử dụng một người bạn hoặc một chức năng thành viên, ông nói:Có nên đi theo chức năng của thành viên hoặc chức năng bạn bè khi chức năng được cho là thay đổi trạng thái của đối tượng không?

Nếu inv() thực sự đảo ngược Ma trận m, thay vì hơn là trả về một Ma trận mới là nghịch đảo của m, nó phải là một thành viên.

Tại sao lại như vậy? Chức năng của bạn không thể thay đổi trạng thái của Ma trận và trả lại tham chiếu cho ma trận đó? Có phải vì khả năng truyền ma trận tạm thời khi chúng tôi gọi hàm này không? ..

+1

Có thể có sự đóng gói rõ ràng hơn về chức năng bên trong đối tượng thay vì lan truyền trong các hàm bạn bè khác nhau. – PherricOxide

+2

Liên quan http://stackoverflow.com/a/7821482/46642 –

+0

Tôi nghĩ rằng câu trả lời xứng đáng với upvotes ở đây nên chỉ ra sự khác biệt thực sự giữa các hàm thành viên và bạn bè không phải thành viên và giải thích cách những khác biệt đó sẽ góp phần đưa ra quyết định chứ không phải vẫy tất cả mọi thứ bằng "vì nó cung cấp đóng gói nhiều hơn/ít hơn". –

Trả lời

9

Thành thật mà nói, tôi nghĩ rằng những lý do duy nhất để đưa ra quyết định như vậy là cú pháp thuận tiện và truyền thống. Tôi sẽ giải thích lý do tại sao bằng cách hiển thị (không) sự khác biệt giữa hai và sự khác biệt này quan trọng như thế nào khi đưa ra quyết định.

Có sự khác biệt nào giữa các chức năng bạn bè không phải thành viên và các chức năng của thành viên công cộng? Không nhiều. Sau khi tất cả, một hàm thành viên chỉ là một hàm bình thường với một tham số this ẩn và truy cập vào các thành viên riêng của lớp.

// what is the difference between the two inv functions? 
// --- our code --- 
struct matrix1x1 { // this one is simple :P 
private: 
    double x; 
public: 
    //... blah blah 
    void inv() { x = 1/x; } 
    friend void inv(matrix1x1& self) { self.x = 1/self.x; } 
}; 
matrix1x1 a; 

// --- client code --- 

// pretty much just this: 
a.inv(); 
// vs this: 
inv(a); 

void lets_try_to_break_encapsulation(matrix1x1& thingy) { 
    thingy.x = 42; // oops, error. Nope, still encapsulated. 
} 

Cả hai đều cung cấp chức năng giống nhau và không thay đổi những chức năng khác có thể làm. Cùng một bên nhận được tiếp xúc với thế giới bên ngoài: không có sự khác biệt về mặt đóng gói.Hoàn toàn không có gì mà các chức năng khác có thể làm khác nhau bởi vì có một hàm bạn bè sửa đổi trạng thái riêng tư. Trong thực tế, người ta có thể viết hầu hết các lớp với hầu hết các chức năng như chức năng người bạn không phải thành viên (chức năng ảo và một số toán tử quá tải phải là thành viên) cung cấp cùng một số lượng đóng gói: người dùng không thể viết bất kỳ chức năng người bạn nào khác mà không sửa đổi và không có chức năng nào ngoài chức năng của bạn bè có thể truy cập các thành viên riêng tư. Tại sao chúng ta không làm điều đó? Bởi vì nó sẽ chống lại phong cách của 99,99% lập trình viên C++ và không có lợi thế lớn để được lấy từ nó.

Sự khác biệt nằm trong bản chất của các chức năng và cách bạn gọi chúng. Là một hàm thành viên có nghĩa là bạn có thể lấy một con trỏ đến hàm thành viên từ nó, và là một hàm không phải là thành viên có nghĩa là bạn có thể nhận được một con trỏ hàm tới nó. Nhưng đó là hiếm khi có liên quan (đặc biệt là với hàm bao hàm chung chung như std::function xung quanh).

Sự khác biệt còn lại là cú pháp. Các nhà thiết kế của ngôn ngữ D quyết định chỉ thống nhất toàn bộ điều và nói rằng bạn có thể gọi một hàm thành viên trực tiếp bằng cách truyền cho nó một đối tượng như inv(a) và gọi hàm miễn phí làm thành viên của đối số đầu tiên, như a.inv(). Và không có lớp học đột nhiên bị đóng gói nặng vì điều đó hay bất cứ điều gì.

Để giải quyết những ví dụ cụ thể trong câu hỏi, nên inv là một thành viên hoặc một tổ chức phi thành viên? Tôi có thể làm cho nó trở thành một thành viên, đối với các đối số thú vị mà tôi đã nêu ở trên. Không theo phong cách, nó không tạo nên sự khác biệt.


. Điều này dường như không xảy ra trong C++ bởi vì vào thời điểm này nó sẽ là một thay đổi phá vỡ, không có lợi ích đáng kể. Nó sẽ, cho một ví dụ cực đoan, phá vỡ các lớp học matrix1x1 tôi đã viết ở trên bởi vì nó làm cho cả hai cuộc gọi mơ hồ.

+0

Bạn đã cung cấp một trường hợp khá kín của một chức năng của bạn bè, nhưng còn khi ai đó làm cho một người bạn trong lớp học thì sao? Nó có thể được lập luận rằng điều đó thực sự làm giảm hoặc thậm chí phá vỡ đóng gói. – Brady

+0

Đối với những gì là giá trị câu hỏi là về chức năng thành viên vs chức năng người bạn (nó không có ý nghĩa gì cả cho các lớp bạn bè). Nếu đó là về các lớp tôi vẫn yêu cầu một ví dụ về điều đó để chấp nhận yêu cầu đó anyway. –

+0

Điểm tốt. Câu trả lời của tôi là nói đến bạn bè nói chung (lớp và chức năng). STL sử dụng chức năng của bạn bè ở nhiều nơi, nhưng tôi không nghĩ rằng nó sử dụng các lớp bạn bè. – Brady

0

Triết lý đóng gói vốn có của OOD (mà C++ cố gắng quảng cáo) chỉ ra rằng trạng thái đối tượng chỉ có thể được sửa đổi từ bên trong. Đó là cú pháp chính xác (trình biên dịch cho phép nó) nhưng cần tránh.

Rất khó để cho phép các phần tử trong hệ thống thay đổi lẫn nhau mà không sử dụng giao diện được xác định trước. Lưu trữ và chức năng đối tượng có thể thay đổi và tìm kiếm mã (có thể rất lớn) sử dụng một phần cụ thể của đối tượng sẽ là một cơn ác mộng.

0

Có hai lập luận đối lập liên quan đến sử dụng bạn bè:

Một bên nói bạn bè giảm đóng gói bởi vì bây giờ bạn đang để cho các đối tượng bên ngoài truy cập vào bên trong của một lớp và internals chỉ nên được sửa đổi bằng các phương pháp thành viên. Mặt khác nói rằng bạn bè thực sự có thể tăng cường đóng gói, vì bạn có thể cấp quyền truy cập vào nội bộ của lớp cho một nhóm nhỏ các thực thể bên ngoài, do đó giảm bớt sự cần thiết phải làm cho các thuộc tính lớp bên trong công khai cho mọi người để các thực thể bên ngoài này có thể truy cập/thao tác chúng.

Cả hai bên đều có thể được lập luận, nhưng tôi có xu hướng đồng ý với tùy chọn đầu tiên.

Đối với câu hỏi của bạn, nó là PherricOxide được đề cập trong bình luận của mình: Nếu các thuộc tính bên trong của một lớp cần phải được sửa đổi, tốt hơn nó được thực hiện bởi một phương pháp thành viên, do đó thực thi đóng gói. Đây là nội tuyến với tùy chọn đầu tiên được đề cập ở trên.

+0

Tại sao downvote ?? – Brady