Có một đối tượng gốc giới hạn những gì bạn có thể làm và những gì trình biên dịch có thể làm, mà không phải trả nhiều tiền.
Một lớp gốc chung cho phép tạo các thùng chứa bất cứ thứ gì và trích xuất những gì chúng có với một dynamic_cast
, nhưng nếu bạn cần các thùng chứa bất cứ thứ gì thì một cái gì đó giống như boost::any
có thể làm điều đó mà không cần một lớp gốc chung. Và boost::any
cũng hỗ trợ các nguyên thủy - nó thậm chí có thể hỗ trợ tối ưu hóa bộ đệm nhỏ và khiến chúng gần như "không được đóng hộp" theo cách nói của Java.
C ++ hỗ trợ và phát triển mạnh về các loại giá trị. Cả hai loại chữ và lập trình viên viết các loại giá trị. C ++ chứa hiệu quả lưu trữ, sắp xếp, băm, tiêu thụ và sản xuất các loại giá trị.
Kế thừa, đặc biệt là kiểu các lớp cơ sở kiểu Java kế thừa nguyên khối ngụ ý, yêu cầu các kiểu "con trỏ" hoặc "tham chiếu" dựa trên cửa hàng miễn phí. Tay cầm / con trỏ / tham chiếu đến dữ liệu của bạn giữ một con trỏ tới giao diện của lớp và đa hình có thể đại diện cho thứ khác.
Mặc dù điều này hữu ích trong một số trường hợp, một khi bạn đã kết hôn với mẫu có "lớp cơ sở chung", bạn đã khóa toàn bộ cơ sở mã của mình vào chi phí và hành lý của mẫu này, ngay cả khi nó không hữu ích.
Hầu như bạn luôn biết nhiều hơn về một loại hơn là "nó là một đối tượng" tại trang web gọi điện hoặc trong mã sử dụng nó.
Nếu chức năng này đơn giản, việc viết hàm dưới dạng mẫu sẽ cung cấp cho bạn tính đa hình dựa trên thời gian biên dịch kiểu con vịt trong đó thông tin tại trang gọi sẽ không bị vứt đi. Nếu chức năng phức tạp hơn, việc xóa kiểu có thể được thực hiện theo đó các thao tác thống nhất trên loại bạn muốn thực hiện (giả sử, tuần tự hóa và giải tuần tự hóa) có thể được xây dựng và lưu trữ (tại thời gian biên dịch) để tiêu thụ (tại thời gian chạy) mã trong một đơn vị dịch thuật khác nhau.
Giả sử bạn có một số thư viện nơi bạn muốn mọi thứ được tuần tự hóa. Một cách tiếp cận là có một lớp cơ sở:
struct serialization_friendly {
virtual void write_to( my_buffer* ) const = 0;
virtual void read_from( my_buffer const* ) = 0;
virtual ~serialization_friendly() {}
};
Bây giờ mỗi bit mã bạn viết có thể được serialization_friendly
.
void serialize( my_buffer* b, serialization_friendly const* x ) {
if (x) x->write_to(b);
}
Ngoại trừ không phải là một std::vector
, vì vậy bây giờ bạn cần phải viết mỗi container. Và không phải những số nguyên bạn có được từ thư viện bignum đó. Và không phải là loại bạn đã viết mà bạn không nghĩ cần tuần tự hóa. Và không phải là một tuple
, hoặc một int
hoặc a double
, hoặc a std::ptrdiff_t
.
Chúng tôi thực hiện một cách tiếp cận khác:
void write_to( my_buffer* b, int x ) {
b->write_integer(x);
}
template<class T,
class=std::enable_if_t< void_t<
std::declval<T const*>()->write_to( std::declval<my_buffer*>()
> >
>
void write_to( my_buffer* b, T const* x ) {
if (x) x->write_to(b);
}
template<class T>
void serialize( my_buffer* b, T const& t ) {
write_to( b, t );
}
trong đó bao gồm, tốt, không làm gì, dường như. Ngoại trừ bây giờ chúng ta có thể mở rộng write_to
bằng cách ghi đè write_to
dưới dạng một hàm miễn phí trong không gian tên của một loại hoặc một phương thức trong loại.
Chúng tôi thậm chí có thể viết một chút mã xóa:
namespace details {
struct can_serialize_pimpl {
virtual void write_to( my_buffer* ) const = 0;
virtual void read_from( my_buffer const* ) = 0;
virtual ~can_serialize_pimpl() {}
};
}
struct can_serialize {
void write_to( my_buffer* b ) const { pImpl->write_to(b); }
void read_from( my_buffer const* b ) { pImpl->read_from(b); }
std::unique_ptr<details::can_serialize_pimpl> pImpl;
template<class T> can_serialize(T&&);
};
namespace details {
template<class T>
struct can_serialize : can_serialize_pimpl {
std::decay_t<T>* t;
void write_to( my_buffer*b ) const final override {
serialize( b, std::forward<T>(*t) );
}
void read_from( my_buffer const* ) final override {
deserialize( b, std::forward<T>(*t) );
}
can_serialize(T&& in):t(&in) {}
};
}
template<class T> can_serialize::can_serialize<T>(T&&t):pImpl(
std::make_unique<details::can_serialize<T>>( std::forward<T>(t) );
) {}
và bây giờ chúng ta có thể lấy một loại tùy ý và tự động đóng hộp nó vào một can_serialize
giao diện cho phép bạn gọi serialize
vào một điểm sau thông qua một giao diện ảo.
Vì thế:
void writer_thingy( can_serialize s );
là một hàm lấy bất cứ thứ gì có thể tuần tự hóa, thay vì
void writer_thingy( serialization_friendly const* s );
và là người đầu tiên, không giống như các thứ hai, nó có thể xử lý int
, std::vector<std::vector<Bob>>
tự động.
Nó không mất nhiều thời gian để viết nó, đặc biệt bởi vì loại điều này là thứ mà bạn hiếm khi muốn làm, nhưng chúng tôi đã có được khả năng coi bất cứ thứ gì là tuần tự hóa mà không yêu cầu loại cơ sở.
Hơn nữa, bây giờ chúng ta có thể thực hiện std::vector<T>
tuần tự hóa như một công dân hạng nhất bằng cách ghi đè đơn giản write_to( my_buffer*, std::vector<T> const& )
- với sự quá tải đó, nó có thể được chuyển đến một can_serialize
và tính tuần tự của std::vector
được lưu trữ trong vtable và được truy cập bởi .write_to
.
Nói tóm lại, C ++ đủ mạnh để bạn có thể thực hiện các lợi thế của một lớp cơ sở duy nhất khi đang cần, mà không phải trả giá của hệ thống phân cấp thừa kế bắt buộc khi không cần thiết. Và thời gian mà cơ sở duy nhất (giả hoặc không) được yêu cầu là rất hiếm.
Khi các loại thực sự là danh tính của chúng, và bạn biết chúng là gì, cơ hội tối ưu hóa rất nhiều. Dữ liệu được lưu trữ cục bộ và liên tục (rất quan trọng đối với tính thân thiện của bộ đệm trên các bộ xử lý hiện đại), trình biên dịch có thể dễ dàng hiểu được thao tác đã cho (thay vì có một con trỏ phương thức ảo mờ mà nó phải nhảy qua, dẫn đến mã không xác định trên mặt khác) cho phép các hướng dẫn được sắp xếp lại một cách tối ưu và ít chốt tròn hơn được đóng vào các lỗ tròn.