Các lớp lồng nhau giống như các lớp thông thường, nhưng:
- họ có hạn chế truy cập bổ sung (như tất cả các định nghĩa trong định nghĩa lớp làm),
- họ không làm ô nhiễm không gian tên đã cho , ví dụ như không gian tên toàn cầu. Nếu bạn cảm thấy rằng lớp B được kết nối sâu sắc với lớp A, nhưng các đối tượng của A và B không nhất thiết phải liên quan với nhau, thì bạn có thể muốn lớp B chỉ có thể truy cập được thông qua phạm vi lớp A (nó sẽ được gọi là A ::Lớp học).
Vài ví dụ:
Lớp lồng nhau công khai để đặt nó trong một phạm vi của lớp có liên quan
Giả sử bạn muốn có một lớp SomeSpecificCollection
sẽ tổng hợp các đối tượng của lớp Element
. Sau đó, bạn có thể:
khai báo hai lớp: SomeSpecificCollection
và Element
- xấu, vì tên "Element" đủ chung để gây ra xung đột tên có thể
giới thiệu một không gian tên someSpecificCollection
và khai báo các lớp someSpecificCollection::Collection
và someSpecificCollection::Element
. Không có nguy cơ đụng độ tên, nhưng nó có thể nhận được nhiều dài dòng hơn không?
khai báo hai lớp toàn cầu SomeSpecificCollection
và SomeSpecificCollectionElement
- có nhược điểm nhỏ, nhưng có lẽ là ổn.
tuyên bố lớp toàn cầu SomeSpecificCollection
và lớp Element
là lớp lồng nhau của nó. Sau đó:
- bạn không có nguy cơ đụng độ tên vì Element không có trong không gian tên toàn cầu,
- trong quá trình thực hiện,
SomeSpecificCollection
bạn chỉ nói đến Element
, và mọi nơi khác như SomeSpecificCollection::Element
- trông + - giống như 3., nhưng rõ ràng hơn
- thật đơn giản rằng đó là "một yếu tố của một bộ sưu tập cụ thể", chứ không phải "một yếu tố cụ thể của một bộ sưu tập"
- nó có thể nhìn thấy đó
SomeSpecificCollection
cũng là một lớp
Theo tôi, biến thể cuối cùng chắc chắn là thiết kế trực quan nhất và do đó tốt nhất.
Hãy để tôi nhấn mạnh - Đó không phải là một sự khác biệt lớn so với việc tạo ra hai lớp toàn cầu với nhiều tên dài dòng hơn. Nó chỉ là một chi tiết nhỏ, nhưng imho nó làm cho mã rõ ràng hơn.
Giới thiệu một phạm vi khác trong phạm vi lớp
Điều này đặc biệt hữu ích để giới thiệu typedefs hoặc enums. Tôi sẽ chỉ đăng một ví dụ mã ở đây:
class Product {
public:
enum ProductType {
FANCY, AWESOME, USEFUL
};
enum ProductBoxType {
BOX, BAG, CRATE
};
Product(ProductType t, ProductBoxType b, String name);
// the rest of the class: fields, methods
};
Một người sau đó sẽ gọi:
Product p(Product::FANCY, Product::BOX);
Nhưng khi xem xét các đề xuất hoàn thành mã Product::
, người ta thường sẽ nhận được tất cả các giá trị enum có thể (BOX, FANCY, CRATE) được liệt kê và rất dễ mắc lỗi ở đây (C ++ 0x được đánh máy mạnh mẽ để giải quyết vấn đề đó, nhưng đừng bận tâm ).
Nhưng nếu bạn giới thiệu phạm vi bổ sung cho những enum sử dụng các lớp lồng nhau, mọi thứ có thể trông như sau:
class Product {
public:
struct ProductType {
enum Enum { FANCY, AWESOME, USEFUL };
};
struct ProductBoxType {
enum Enum { BOX, BAG, CRATE };
};
Product(ProductType::Enum t, ProductBoxType::Enum b, String name);
// the rest of the class: fields, methods
};
Sau đó, cuộc gọi trông như:
Product p(Product::ProductType::FANCY, Product::ProductBoxType::BOX);
Sau đó, bằng cách gõ Product::ProductType::
vào một IDE, người ta sẽ chỉ nhận được các enum từ phạm vi mong muốn được đề xuất. Điều này cũng làm giảm nguy cơ mắc lỗi.
Tất nhiên điều này có thể không cần thiết cho các lớp nhỏ, nhưng nếu một người có nhiều enum, thì nó sẽ giúp mọi thứ dễ dàng hơn cho các lập trình viên máy khách.
Theo cách tương tự, bạn có thể "tổ chức" một loạt các typedefs trong một mẫu, nếu bạn có nhu cầu. Đôi khi nó là một mẫu hữu ích.
Thành ngữ PIMPL
PIMPL (viết tắt của Con trỏ đến IMPLementation) là một thành ngữ hữu ích để loại bỏ các chi tiết triển khai của một lớp khỏi tiêu đề. Điều này làm giảm nhu cầu biên dịch lại các lớp tùy thuộc vào tiêu đề của lớp bất cứ khi nào phần "triển khai" của tiêu đề thay đổi.
Nó thường được thực hiện bằng cách sử dụng một lớp lồng nhau:
Xh:
class X {
public:
X();
virtual ~X();
void publicInterface();
void publicInterface2();
private:
struct Impl;
std::unique_ptr<Impl> impl;
}
X.cpp:
#include "X.h"
#include <windows.h>
struct X::Impl {
HWND hWnd; // this field is a part of the class, but no need to include windows.h in header
// all private fields, methods go here
void privateMethod(HWND wnd);
void privateMethod();
};
X::X() : impl(new Impl()) {
// ...
}
// and the rest of definitions go here
Điều này đặc biệt hữu ích nếu định nghĩa lớp đầy đủ cần định nghĩa về các loại từ một thư viện bên ngoài có tệp tiêu đề nặng hoặc chỉ xấu (lấy WinAPI). Nếu bạn sử dụng PIMPL, thì bạn có thể chỉ bao gồm bất kỳ chức năng cụ thể nào của WinAPI .cpp
và không bao giờ bao gồm nó .h
.