Câu hỏi nhanh: từ quan điểm thiết kế, tại sao trong C ++, không có lớp cơ sở mẹ của tất cả, những gì thường có object
trong các ngôn ngữ khác?
typedef
. Với hệ thống phân cấp lớp chung chung hơn, bạn chỉ có thể sử dụng object
hoặc iterator
.
Câu hỏi nhanh: từ quan điểm thiết kế, tại sao trong C ++, không có lớp cơ sở mẹ của tất cả, những gì thường có object
trong các ngôn ngữ khác?
typedef
. Với hệ thống phân cấp lớp chung chung hơn, bạn chỉ có thể sử dụng object
hoặc iterator
.
Câu trả lời:
Phán quyết cuối cùng được tìm thấy trong Câu hỏi thường gặp của Stroustrup . Tóm lại, nó không truyền tải bất kỳ ý nghĩa ngữ nghĩa nào. Nó sẽ có một chi phí. Các mẫu hữu ích hơn cho các thùng chứa.
Tại sao C ++ không có Đối tượng lớp phổ quát?
Chúng tôi không cần một: lập trình chung cung cấp các lựa chọn thay thế an toàn kiểu tĩnh trong hầu hết các trường hợp. Các trường hợp khác được xử lý bằng cách sử dụng đa kế thừa.
Không có lớp phổ quát hữu ích: lớp phổ quát thực sự không mang ngữ nghĩa của riêng nó.
Một lớp "phổ thông" khuyến khích suy nghĩ cẩu thả về các loại và giao diện và dẫn đến việc kiểm tra thời gian chạy quá mức.
Sử dụng một lớp cơ sở phổ quát ngụ ý chi phí: Các đối tượng phải được phân bổ theo đống để có thể đa hình; điều đó có nghĩa là bộ nhớ và chi phí truy cập. Các đối tượng Heap không hỗ trợ ngữ nghĩa sao chép một cách tự nhiên. Các đối tượng Heap không hỗ trợ hành vi theo phạm vi đơn giản (điều này làm phức tạp việc quản lý tài nguyên). Một lớp cơ sở phổ quát khuyến khích sử dụng dynamic_cast và kiểm tra thời gian chạy khác.
Objects must be heap-allocated to be polymorphic
- Tôi không nghĩ câu nói này nói chung là đúng. Bạn chắc chắn có thể tạo một thể hiện của lớp đa hình trên ngăn xếp và chuyển nó dưới dạng con trỏ đến một trong các lớp cơ sở của nó, mang lại hành vi đa hình.
Foo
thành một tham chiếu đến- Bar
khi Bar
nào không kế thừa Foo
, về mặt xác định sẽ ném ra một ngoại lệ. Trong C ++, việc cố gắng biến một con trỏ tới Foo thành một con trỏ tới Thanh có thể gửi một robot quay ngược thời gian để giết Sarah Connor.
Trước tiên, hãy nghĩ về lý do tại sao bạn muốn có một lớp cơ sở ngay từ đầu. Tôi có thể nghĩ ra một số lý do khác nhau:
Đây là hai lý do chính đáng mà các ngôn ngữ của thương hiệu Smalltalk, Ruby và Objective-C có các lớp cơ sở (về mặt kỹ thuật, Objective-C không thực sự có lớp cơ sở, nhưng đối với mọi ý định và mục đích, nó có).
Đối với # 1, nhu cầu về một lớp cơ sở hợp nhất tất cả các đối tượng trong một giao diện duy nhất đã bị loại bỏ bởi việc đưa các mẫu vào C ++. Ví dụ:
void somethingGeneric(Base);
Derived object;
somethingGeneric(object);
là không cần thiết, khi bạn có thể duy trì tính toàn vẹn của kiểu thông qua phương tiện đa hình tham số!
template <class T>
void somethingGeneric(T);
Derived object;
somethingGeneric(object);
Đối với # 2, trong khi trong Objective-C, các thủ tục quản lý bộ nhớ là một phần của việc triển khai một lớp và được kế thừa từ lớp cơ sở, quản lý bộ nhớ trong C ++ được thực hiện bằng cách sử dụng thành phần thay vì kế thừa. Ví dụ: bạn có thể xác định một trình bao bọc con trỏ thông minh sẽ thực hiện đếm tham chiếu trên các đối tượng thuộc bất kỳ loại nào:
template <class T>
struct refcounted
{
refcounted(T* object) : _object(object), _count(0) {}
T* operator->() { return _object; }
operator T*() { return _object; }
void retain() { ++_count; }
void release()
{
if (--_count == 0) { delete _object; }
}
private:
T* _object;
int _count;
};
Sau đó, thay vì gọi các phương thức trên chính đối tượng, bạn sẽ gọi các phương thức trong trình bao bọc của nó. Điều này không chỉ cho phép lập trình chung chung hơn: nó còn cho phép bạn tách biệt các mối quan tâm (vì lý tưởng, đối tượng của bạn nên quan tâm nhiều hơn đến những gì nó phải làm, hơn là cách quản lý bộ nhớ của nó trong các tình huống khác nhau).
Cuối cùng, trong một ngôn ngữ có cả đối tượng gốc và đối tượng thực tế như C ++, lợi ích của việc có một lớp cơ sở (một giao diện nhất quán cho mọi giá trị) sẽ bị mất, do đó bạn có một số giá trị không thể phù hợp với giao diện đó. Để sử dụng nguyên thủy trong loại tình huống đó, bạn cần nâng chúng thành các đối tượng (nếu trình biên dịch của bạn không tự động làm điều đó). Điều này tạo ra rất nhiều phức tạp.
Vì vậy, câu trả lời ngắn gọn cho câu hỏi của bạn: C ++ không có lớp cơ sở bởi vì, có đa hình tham số thông qua các mẫu, nó không cần thiết.
object
( System.Object
), nhưng chúng không cần phải như vậy. Đối với trình biên dịch, int
và System.Int32
là bí danh và có thể được sử dụng thay thế cho nhau; thời gian chạy xử lý quyền anh khi cần thiết.
std::shared_ptr
nên được sử dụng thay thế.
Mô hình chi phối cho các biến C ++ là truyền qua giá trị, không phải chuyển theo tham chiếu. Việc buộc mọi thứ phải bắt nguồn từ gốc Object
sẽ khiến việc chuyển chúng theo giá trị thành một lỗi ipse facto.
(Bởi vì việc chấp nhận một Đối tượng theo giá trị làm tham số, theo định nghĩa sẽ cắt nó và loại bỏ linh hồn của nó).
Đây là điều không được hoan nghênh. C ++ khiến bạn suy nghĩ về việc bạn muốn giá trị hay ngữ nghĩa tham chiếu, cho bạn sự lựa chọn. Đây là một điều quan trọng trong tính toán hiệu suất.
Object
sẽ tương đối nhỏ.
Vấn đề là có một kiểu như vậy trong C ++! Đó là void
. :-) Bất kỳ con trỏ nào cũng có thể được truyền ngầm đến một cách an toàn void *
, bao gồm các con trỏ đến các kiểu cơ bản, các lớp không có bảng ảo và các lớp có bảng ảo.
Vì nó phải tương thích với tất cả các loại đối tượng đó, nên void
bản thân nó không thể chứa các phương thức ảo. Nếu không có các hàm ảo và RTTI, không có thông tin hữu ích nào về loại có thể được lấy từ void
(nó khớp với MỌI loại, vì vậy chỉ có thể cho biết những thứ đúng với MỌI loại), nhưng các hàm ảo và RTTI sẽ làm cho các loại đơn giản trở nên rất kém hiệu quả và ngăn cản C ++ một ngôn ngữ thích hợp cho lập trình cấp thấp với khả năng truy cập bộ nhớ trực tiếp, v.v.
Vì vậy, có loại như vậy. Nó chỉ cung cấp giao diện rất tối giản (thực tế là trống) do tính chất cấp thấp của ngôn ngữ. :-)
void
.
void
nó sẽ không biên dịch và bạn gặp lỗi này . Trong Java, bạn có thể có một biến kiểu Object
và nó sẽ hoạt động. Đó là sự khác biệt giữa void
và một loại "thực". Có lẽ đúng void
là kiểu cơ sở cho mọi thứ, nhưng vì nó không cung cấp bất kỳ hàm tạo, phương thức hoặc trường nào nên không có cách nào để biết nó có ở đó hay không. Khẳng định này không thể được chứng minh hoặc bác bỏ.
void
là một kiểu (và phát hiện ra một cách sử dụng? :
hơi thông minh ), nhưng nó vẫn còn lâu mới trở thành một lớp cơ sở phổ quát. Đối với một điều, nó là "một loại không hoàn chỉnh không thể hoàn thành," và cho một loại khác, không có void&
loại nào .
C ++ là một ngôn ngữ được đánh máy mạnh. Tuy nhiên, thật khó hiểu là nó không có kiểu đối tượng phổ quát trong bối cảnh chuyên biệt hóa khuôn mẫu.
Lấy ví dụ, mẫu
template <class T> class Hook;
template <class ReturnType, class ... ArgTypes>
class Hook<ReturnType (ArgTypes...)>
{
...
ReturnType operator () (ArgTypes... args) { ... }
};
có thể được khởi tạo như
Hook<decltype(some_function)> ...;
Bây giờ, hãy giả sử rằng chúng ta muốn điều tương tự cho một hàm cụ thể. Giống
template <auto fallback> class Hook;
template <auto fallback, class ReturnType, class ... ArgTypes>
class Hook<ReturnType fallback(ArgTypes...)>
{
...
ReturnType operator () (ArgTypes... args) { ... }
};
với sự khởi tạo chuyên biệt
Hook<some_function> ...
Nhưng than ôi, mặc dù lớp T có thể thay thế cho bất kỳ kiểu nào (lớp hoặc không) trước chuyên môn hóa, không có từ nào tương đương auto fallback
(tôi đang sử dụng cú pháp đó như cú pháp không phải kiểu chung chung rõ ràng nhất trong ngữ cảnh này) có thể thay thế cho bất kỳ đối số mẫu không phải kiểu trước khi chuyên môn hóa.
Vì vậy, nói chung mẫu này không chuyển từ đối số mẫu kiểu sang đối số mẫu không phải kiểu.
Giống như rất nhiều góc trong ngôn ngữ C ++, câu trả lời có thể là "không có thành viên ủy ban nào nghĩ ra".
ReturnType fallback(ArgTypes...)
sẽ hoạt động, nó sẽ là một thiết kế tồi. template <class T, auto fallback> class Hook; template <class ReturnType, class ... ArgTypes> class Hook<ReturnType (ArgTypes...), ReturnType(*fallback)(ArgTypes...)> ...
làm những gì bạn muốn và có nghĩa là các thông số mẫu của Hook
là của phù hợp các loại
C ++ ban đầu được gọi là "C với các lớp". Nó là một bước tiến của ngôn ngữ C, không giống như một số thứ hiện đại hơn khác như C #. Và bạn không thể xem C ++ là một ngôn ngữ, mà là nền tảng của ngôn ngữ (Vâng, tôi đang nhớ cuốn sách của Scott Meyers Hiệu quả C ++).
Bản thân C là sự kết hợp của nhiều ngôn ngữ, ngôn ngữ lập trình C và bộ tiền xử lý của nó.
C ++ thêm một hỗn hợp khác:
phương pháp tiếp cận lớp / đối tượng
mẫu
STL
Cá nhân tôi không thích một số thứ đến trực tiếp từ C sang C ++. Một ví dụ là tính năng enum. Cách C # cho phép nhà phát triển sử dụng nó tốt hơn: nó giới hạn enum trong phạm vi riêng của nó, nó có thuộc tính Count và có thể dễ dàng lặp lại.
Vì C ++ muốn tương thích ngược với C, nhà thiết kế đã rất dễ dãi cho phép ngôn ngữ C nhập toàn bộ vào C ++ (có một số khác biệt nhỏ, nhưng tôi không nhớ bất kỳ điều gì bạn có thể làm bằng trình biên dịch C mà bạn không thể làm bằng trình biên dịch C ++).