Tại sao cơ sở cho tất cả các đối tượng không được khuyến khích trong C ++


76

Stroustrup nói "Đừng ngay lập tức phát minh ra một cơ sở duy nhất cho tất cả các lớp của bạn (một lớp Object). Thông thường, bạn có thể làm tốt hơn mà không cần nó cho nhiều / hầu hết các lớp." (Phiên bản thứ tư của ngôn ngữ lập trình C ++, Sec 1.3.4)

Tại sao một lớp cơ sở cho tất cả mọi thứ nói chung là một ý tưởng tồi, và khi nào nó có ý nghĩa để tạo ra một ý tưởng?


16
bởi vì C ++ không phải là Java ... Và bạn không nên cố gắng ép buộc nó.
AK_ 16/2/2015

10
Đã hỏi về Stack Overflow: Tại sao không có lớp cơ sở trong C ++?

26
Ngoài ra, tôi không đồng ý với số phiếu sát sao cho "chủ yếu dựa trên ý kiến". Có những lý do rất cụ thể có thể được giải thích cho điều này, vì các câu trả lời chứng thực cả về câu hỏi này và câu hỏi SO được liên kết.

2
Đó là nguyên tắc "bạn sẽ không cần nó" nhanh nhẹn. Trừ khi bạn đã xác định được một nhu cầu cụ thể cho nó rồi, đừng làm điều đó (cho đến khi bạn làm được).
Jool

3
@AK_: Có một "ngu ngốc như" trong bình luận của bạn.
DeadMG

Câu trả lời:


75

Bởi vì những gì đối tượng sẽ có chức năng? Trong java, tất cả các lớp Base có toString, hashCode & Equality và biến điều khiển + biến điều kiện.

  • ToString chỉ hữu ích để gỡ lỗi.

  • hashCode chỉ hữu ích nếu bạn muốn lưu trữ nó trong bộ sưu tập dựa trên hàm băm (ưu tiên trong C ++ là chuyển một hàm băm cho vùng chứa dưới dạng param param hoặc để tránh std::unordered_*hoàn toàn và thay vào đó sử dụng std::vectorvà liệt kê các danh sách không có thứ tự).

  • bình đẳng không có đối tượng cơ sở có thể được trợ giúp tại thời điểm biên dịch, nếu chúng không có cùng loại thì chúng không thể bằng nhau. Trong C ++, đây là lỗi thời gian biên dịch.

  • màn hình và biến điều kiện tốt hơn được bao gồm rõ ràng trong từng trường hợp.

Tuy nhiên, khi có nhiều việc cần làm thì sẽ có trường hợp sử dụng.

Ví dụ, trong QT có QObjectlớp gốc tạo thành cơ sở của mối quan hệ luồng, hệ thống phân cấp sở hữu cha-con và cơ chế khe tín hiệu. Nó cũng buộc sử dụng bởi con trỏ cho QObject, tuy nhiên, nhiều Class trong Qt không kế thừa QObject vì chúng không cần khe tín hiệu (đặc biệt là các loại giá trị của một số mô tả).


7
Bạn đã quên đề cập đến có lẽ lý do chính khiến Java có một lớp cơ sở: Trước khi tạo tổng quát, các lớp tập hợp cần lớp cơ sở để hoạt động. Mọi thứ (bộ nhớ trong, tham số, giá trị trả về) đã được gõ Object.
Alexanderr Dubinsky

1
@AleksandrDubinsky: Và thuốc generic chỉ thêm đường cú pháp, không thực sự thay đổi bất kỳ thứ gì ngoài việc đánh bóng.
Ded repeatator

4
Tôi sẽ tranh luận, mã băm, bình đẳng và hỗ trợ giám sát là những lỗi thiết kế trong Java. Ai nghĩ rằng đó là một ý tưởng tốt để làm cho tất cả các đối tượng một khóa?!
usr

1
Vâng, nhưng không ai muốn nó. Lần cuối bạn cần khóa đối tượng là gì và không thể khởi tạo một đối tượng khóa riêng biệt để làm điều đó. Nó rất hiếm và nó đặt gánh nặng lên mọi thứ. Các anh chàng Java đã có một sự hiểu biết không tốt về sự an toàn của luồng khi đó là bằng chứng cho thấy tất cả các đối tượng là một khóa và bởi các bộ sưu tập an toàn của luồng hiện không được chấp nhận. An toàn chủ đề là một tài sản toàn cầu, không phải là một đối tượng.
usr

2
" HashCode chỉ hữu ích nếu bạn muốn lưu trữ nó trong một bộ sưu tập băm dựa trên (các sở thích trong C ++ là dành cho std :: vector và danh sách đơn giản có thứ tự). " Sự phản biện thực sự _hashCodekhông phải là 'sử dụng một container khác nhau' nhưng thay vì chỉ ra rằng C ++ std::unordered_mapkhông băm bằng cách sử dụng một đối số khuôn mẫu, thay vì yêu cầu chính lớp phần tử cung cấp việc thực hiện. Đó là, giống như tất cả các trình quản lý tài nguyên và bộ chứa tốt khác trong C ++, nó không xâm phạm; nó không gây ô nhiễm tất cả các đối tượng có chức năng hoặc dữ liệu trong trường hợp ai đó có thể cần chúng trong một số bối cảnh sau này.
gạch dưới

100

Bởi vì không có chức năng được chia sẻ bởi tất cả các đối tượng. Không có gì để đặt trong giao diện này có ý nghĩa cho tất cả các lớp.


10
+1 vì đơn giản của câu trả lời, đây thực sự là lý do duy nhất.
BWG

7
Trong khung lớn mà tôi có kinh nghiệm, lớp cơ sở chung cung cấp cơ sở hạ tầng tuần tự hóa và phản chiếu được mong muốn trong bối cảnh <anything>. Meh. Nó chỉ dẫn đến việc mọi người tuần tự hóa một loạt các hành trình cùng với dữ liệu và siêu dữ liệu và làm cho định dạng dữ liệu quá lớn và phức tạp để có hiệu quả.
dmckee

19
@dmckee: Tôi cũng cho rằng việc tuần tự hóa và phản ánh hầu như không phải là nhu cầu hữu ích chung.
DeadMG

16
@DeadMG: "NHƯNG GÌ NẾU BẠN CẦN TIẾT KIỆM MỌI THỨ?"
deworde 16/2/2015

8
Tôi không biết, bạn dán nó trong dấu ngoặc kép, bạn sử dụng tất cả các chữ hoa và mọi người không thể thấy trò đùa. @MSalters: Chà, điều đó thật dễ dàng, nó có một lượng trạng thái tối thiểu, bạn chỉ cần xác định rằng nó ở đó. Tôi có thể viết tên của mình vào danh sách mà không cần nhập vòng lặp đệ quy.
deworde 16/2/2015

25

Bất cứ khi nào bạn xây dựng hệ thống phân cấp thừa kế cao của các đối tượng, bạn có xu hướng gặp phải vấn đề của Lớp cơ sở mong manh (Wikipedia.) .

Có nhiều hệ thống phân cấp thừa kế riêng biệt (riêng biệt, tách biệt) làm giảm cơ hội gặp phải vấn đề này.

Làm cho tất cả các đối tượng của bạn là một phần của hệ thống phân cấp thừa kế đơn lẻ thực tế đảm bảo rằng bạn sẽ gặp phải vấn đề này.


6
Khi lớp cơ sở (trong Java "java.lang.Object") không chứa bất kỳ phương thức nào gọi các phương thức khác, vấn đề Lớp cơ sở mong manh không thể xảy ra.
Martin Rosenau

3
Một lớp cơ sở hữu ích hùng mạnh đó sẽ là!
Mike Nakis

9
@MartinRosenau ... giống như bạn có thể làm trong C ++ mà không cần lớp cơ sở chính!
gbjbaanb

5
@ DavorŽdralo Vì vậy, C ++ có một tên ngu ngốc cho một hàm cơ bản ("toán tử <<" thay vì một cái gì đó hợp lý như "DebugPrint"), trong khi Java có một lớp cơ bản cho tất cả các lớp bạn viết, không có ngoại lệ. Tôi nghĩ rằng tôi thích mụn cóc của C ++ hơn.
Sebastian Redl

4
@ DavorŽdralo: Tên của hàm không liên quan. Hình ảnh một cú pháp cout.print(x).print(0.5).print("Bye\n")- nó không xoay quanh operator<<.
MSalters

24

Bởi vì:

  1. Bạn không nên trả tiền cho những gì bạn không sử dụng.
  2. Các hàm này có ý nghĩa ít hơn trong một hệ thống loại dựa trên giá trị so với trong hệ thống loại dựa trên tham chiếu.

Việc thực hiện bất kỳ loại virtualchức năng nào đều giới thiệu một bảng ảo, đòi hỏi chi phí không gian trên mỗi đối tượng không cần thiết cũng không mong muốn trong nhiều tình huống (hầu hết?).

Việc triển khai toStringkhông thường xuyên sẽ khá vô ích, bởi vì điều duy nhất nó có thể trả về là địa chỉ của đối tượng, rất không thân thiện với người dùng và người gọi đã có quyền truy cập, không giống như trong Java.
Tương tự, một người không ảo equalshoặc hashCodechỉ có thể sử dụng địa chỉ để so sánh các đối tượng, một lần nữa khá vô dụng và thậm chí hoàn toàn sai - không giống như trong Java, các đối tượng được sao chép thường xuyên trong C ++ và do đó phân biệt "danh tính" của một đối tượng thậm chí không luôn có ý nghĩa hoặc hữu ích. (ví dụ: một số intthực sự không nên có một danh tính khác với giá trị của nó ... hai số nguyên có cùng giá trị sẽ bằng nhau.)


Liên quan đến vấn đề này và vấn đề lớp cơ sở mong manh được Mike Nakis lưu ý, lưu ý nghiên cứu / đề xuất thú vị về sửa lỗi trong Java về cơ bản bằng cách làm cho tất cả các phương thức bên trong (tức là khi được gọi từ cùng một lớp) không ảo nhưng vẫn giữ hành vi ảo của chúng khi gọi bên ngoài; để có được hành vi cũ / tiêu chuẩn (tức là ảo ở mọi nơi), đề xuất đã giới thiệu một opentừ khóa mới . Tôi không nghĩ rằng nó đã đi bất cứ nơi nào ngoài một vài bài báo.
Fizz

Thảo luận thêm một chút về bài báo đó có thể được tìm thấy tại lambda-the-ultimate.org/ classic / message12271.html
Fizz

Có một lớp cơ sở chung sẽ giúp có thể kiểm tra bất kỳ shared_ptr<Foo> để xem liệu nó cũng là một shared_ptr<Bar>(hoặc tương tự với các loại con trỏ khác), ngay cả khi FooBarlà các lớp không liên quan mà không biết gì về nhau. Yêu cầu một thứ như vậy hoạt động với "con trỏ thô", dựa trên lịch sử về cách sử dụng những thứ đó, sẽ tốn kém, nhưng đối với những thứ sẽ được lưu trữ đống, dù sao chi phí sẽ là tối thiểu.
18/2/2015

Mặc dù có thể không hữu ích khi có một lớp cơ sở chung cho mọi thứ, tôi nghĩ có một số loại đối tượng khá lớn mà các lớp cơ sở chung sẽ hữu ích. Ví dụ, nhiều lớp (đa số đáng kể nếu không phải là đa số) trong Java có thể được sử dụng theo hai cách: với tư cách là người giữ dữ liệu không thể chia sẻ hoặc là người giữ dữ liệu có thể chia sẻ mà không ai được phép sửa đổi. Với cả hai mẫu sử dụng, một con trỏ được quản lý (tham chiếu) được sử dụng làm proxy cho dữ liệu cơ bản. Có thể có một loại con trỏ được quản lý chung cho tất cả các dữ liệu đó là hữu ích.
supercat

16

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::anycó thể làm điều đó mà không cần một lớp gốc chung. Và boost::anycũ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 inthoặ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_tobằng cách ghi đè write_todướ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_serializegiao diện cho phép bạn gọi serializevà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_serializevà 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.


8

Có nhiều câu trả lời hay ở trên, và thực tế rõ ràng rằng bất cứ điều gì bạn sẽ làm với một đối tượng lớp cơ sở đều có thể được thực hiện tốt hơn theo những cách khác như được hiển thị bởi câu trả lời của @ ratchetfreak và các nhận xét về nó là rất quan trọng, nhưng Có một lý do khác, đó là để tránh tạo ra kim cương thừa kếkhi nhiều kế thừa được sử dụng. Nếu bạn có bất kỳ chức năng nào trong một lớp cơ sở phổ quát, ngay khi bạn bắt đầu sử dụng nhiều kế thừa, bạn phải bắt đầu chỉ định biến thể của nó mà bạn muốn truy cập, bởi vì nó có thể bị quá tải theo các đường dẫn khác nhau của chuỗi thừa kế. Và cơ sở không thể là ảo, bởi vì điều này sẽ rất kém hiệu quả (yêu cầu tất cả các đối tượng phải có một bảng ảo với chi phí rất lớn trong việc sử dụng bộ nhớ và địa phương). Điều này sẽ trở thành một cơn ác mộng hậu cần rất nhanh.


1
Một giải pháp cho vấn đề kim cương là có tất cả các loại mà hầu như không lấy được loại cơ sở thông qua nhiều đường dẫn ghi đè tất cả các thành viên ảo của loại cơ sở đó; nếu một loại cơ sở chung đã được tích hợp vào ngôn ngữ ngay từ đầu, trình biên dịch có thể tự động tạo ra các triển khai mặc định hợp pháp (mặc dù không nhất thiết phải ấn tượng).
18/2/2015

5

Trong thực tế, các trình biên dịch và thư viện C ++ đầu tiên của microsofts (tôi biết về Visual C ++, 16 bit) có một lớp như vậy được đặt tên CObject.

Tuy nhiên, bạn phải biết rằng tại thời điểm đó "các mẫu" không được trình biên dịch C ++ đơn giản này hỗ trợ nên các lớp như std::vector<class T>không thể thực hiện được. Thay vào đó, việc triển khai "vectơ" chỉ có thể xử lý một loại lớp nên có một lớp tương đương với std::vector<CObject>ngày nay. Bởi vì CObjectlà lớp cơ sở của gần như tất cả các lớp (không may là không CString- tương đương với các stringtrình biên dịch hiện đại), bạn có thể sử dụng lớp này để lưu trữ gần như tất cả các loại đối tượng.

Bởi vì các trình biên dịch hiện đại hỗ trợ các mẫu, trường hợp sử dụng này của "lớp cơ sở chung" không còn được đưa ra.

Bạn phải suy nghĩ về thực tế rằng việc sử dụng một lớp cơ sở chung như vậy sẽ tốn (một ít) bộ nhớ và thời gian chạy - ví dụ như trong cuộc gọi đến hàm tạo. Vì vậy, có những hạn chế khi sử dụng một lớp như vậy nhưng ít nhất là khi sử dụng các trình biên dịch C ++ hiện đại, gần như không có trường hợp sử dụng nào cho một lớp như vậy.


3
Đó có phải là MFC không? [đệm bình luận]
user253751

3
Nó thực sự là MFC. Một đèn hiệu sáng chói của thiết kế OO cho thế giới thấy mọi thứ nên được thực hiện như thế nào. Ôi, đợi đã ...
gbjbaanb

4
@gbjbaanb Turbo Pascal và Turbo C ++ đã có TObjecttrước khi MFC tồn tại. Đừng đổ lỗi cho Microsoft về phần thiết kế đó, có vẻ như đó là một ý tưởng tốt cho khá nhiều người trong khoảng thời gian đó.
HVD

Ngay cả trước các mẫu, cố gắng viết Smalltalk trong C ++ đã tạo ra kết quả khủng khiếp.
JDługosz

@hvd Tuy nhiên, MFC là một ví dụ tồi tệ hơn nhiều về thiết kế hướng đối tượng so với bất kỳ thứ gì Borland sản xuất.
Jules

5

Tôi sẽ đề xuất một lý do khác đến từ Java.

Bởi vì bạn không thể tạo một lớp cơ sở cho mọi thứ ít nhất là không có một bó nồi hơi.

Bạn có thể có thể thoát khỏi nó cho các lớp học của riêng bạn - nhưng có lẽ bạn sẽ thấy rằng cuối cùng bạn sẽ sao chép rất nhiều mã. Ví dụ: "Tôi không thể sử dụng std::vectorở đây vì nó không triển khai IObject- Tôi nên tạo ra một dẫn xuất mới IVectorObjectthực hiện đúng ...".

Đây sẽ là trường hợp bất cứ khi nào bạn đang xử lý các lớp hoặc thư viện chuẩn hoặc các thư viện từ các thư viện khác.

Bây giờ nếu nó được tích hợp vào ngôn ngữ mà bạn kết thúc bằng những thứ như Integerintsự nhầm lẫn trong java hoặc một thay đổi lớn đối với cú pháp ngôn ngữ. (Hãy nhớ rằng tôi nghĩ rằng một số ngôn ngữ khác đã thực hiện một công việc tốt với việc xây dựng nó thành mọi loại - ruby ​​có vẻ như là một ví dụ tốt hơn.)

Cũng lưu ý rằng nếu lớp cơ sở của bạn không phải là đa hình thời gian chạy (nghĩa là sử dụng các hàm ảo), bạn có thể nhận được lợi ích tương tự từ việc sử dụng một đặc điểm như khung.

ví dụ thay vì .toString()bạn có thể có những điều sau đây: (LƯU Ý: Tôi biết bạn có thể thực hiện công việc này gọn gàng hơn bằng cách sử dụng các thư viện hiện có, v.v.

template<typename T>
struct ToStringTrait;

template<typename T> 
std::string toString(const T & t) {
  return ToStringTrait<T>::toString(t);
}

template<>
struct ToStringTrait<int> {
  std::string toString(int v) {
    return itoa(v);
  }
}

template<typename T>
struct ToStringTrait<std::vector<T>> {
  std::string toString(const std::vector<T> &v) {
    std::stringstream ss;
    ss<<"{";
    for(int i=0; i<v.size(); ++i) {
      ss<<toString(v[i]);
    }
    ss<<"}";
    return ss.str();
  }
}

3

Có thể cho rằng "khoảng trống" hoàn thành rất nhiều vai trò của lớp cơ sở phổ quát. Bạn có thể bỏ bất kỳ con trỏ nào tới a void*. Sau đó bạn có thể so sánh các con trỏ. Bạn có thể static_casttrở lại lớp ban đầu.

Tuy nhiên, những gì bạn không thể làm với voidnhững gì bạn có thể làm Objectlà sử dụng RTTI để tìm ra loại đối tượng bạn thực sự có. Điều này cuối cùng là làm thế nào để không phải tất cả các đối tượng trong C ++ đều có RTTI và thực sự có thể có các đối tượng có độ rộng bằng không.


1
Chỉ các tiểu dự án cơ sở có chiều rộng bằng không, không phải là các mẫu con bình thường.
Ded

@Ded repeatator Bằng cách cập nhật, C ++ 17 cho biết thêm [[no_unique_address]], trình biên dịch có thể được sử dụng để tạo độ rộng bằng không cho các tiểu dự án thành viên.
gạch dưới

1
@underscore_d Bạn có nghĩa là đã lên kế hoạch cho C ++ 20, [[no_unique_address]]sẽ cho phép trình biên dịch thành các biến thành viên EBO.
Ded

@Ded repeatator Rất tiếc, yup. Tôi đã bắt đầu sử dụng C ++ 17, nhưng tôi đoán tôi vẫn nghĩ rằng nó tiên tiến hơn thực tế!
gạch dưới

2

Java có triết lý thiết kế rằng Hành vi không xác định không nên tồn tại . Mã như:

Cat felix = GetCat();
Woofer Rover = (Woofer)felix;
Rover.woof();

sẽ kiểm tra xem felixcó giữ một kiểu con của Catgiao diện đó không Woofer; nếu có, nó sẽ thực hiện các vai diễn và gọi woof()và nếu không, nó sẽ đưa ra một ngoại lệ. Hành vi của mã được xác định đầy đủ cho dù felixthực hiện Wooferhay không .

C ++ đưa ra triết lý rằng nếu một chương trình không nên thực hiện một số thao tác, thì việc mã được tạo sẽ làm gì nếu hoạt động đó được thực hiện và máy tính không nên lãng phí thời gian để cố gắng hạn chế hành vi trong trường hợp "nên" không bao giờ phát sinh Trong C ++, việc thêm các toán tử gián tiếp thích hợp để truyền a *Catđến a *Woofer, mã sẽ mang lại hành vi được xác định khi truyền là hợp pháp, nhưng Hành vi không xác định khi không .

Có một loại cơ sở chung cho mọi thứ giúp xác thực các diễn viên trong số các dẫn xuất của loại cơ sở đó và cũng có thể thực hiện các thao tác thử, nhưng xác thực các diễn viên đắt hơn so với việc đơn giản cho rằng chúng hợp pháp và hy vọng không có gì xấu xảy ra. Triết lý của C ++ là việc xác nhận như vậy đòi hỏi "trả tiền cho những thứ bạn [thường] không cần".

Một vấn đề khác liên quan đến C ++, nhưng sẽ không phải là vấn đề đối với ngôn ngữ mới, đó là nếu một số lập trình viên mỗi người tạo một cơ sở chung, lấy ra các lớp của riêng họ từ đó và viết mã để làm việc với những thứ thuộc lớp cơ sở chung đó, mã đó sẽ không thể làm việc với các đối tượng được phát triển bởi các lập trình viên đã sử dụng một lớp cơ sở khác. Nếu một ngôn ngữ mới yêu cầu tất cả các đối tượng heap có định dạng tiêu đề chung và chưa bao giờ cho phép các đối tượng heap không có, thì một phương thức yêu cầu tham chiếu đến một đối tượng heap với tiêu đề như vậy sẽ chấp nhận tham chiếu đến bất kỳ đối tượng heap nào bao giờ có thể tạo ra.

Cá nhân, tôi nghĩ rằng việc có một phương tiện phổ biến để hỏi một đối tượng "bạn có thể chuyển đổi thành loại X" là một tính năng rất quan trọng trong ngôn ngữ / khung, nhưng nếu tính năng đó không được tích hợp vào ngôn ngữ ngay từ đầu thì rất khó để thêm nó sau Cá nhân, tôi nghĩ rằng một lớp cơ sở như vậy nên được thêm vào một thư viện tiêu chuẩn ngay từ đầu, với một khuyến nghị mạnh mẽ rằng tất cả các đối tượng sẽ được sử dụng đa hình nên kế thừa từ cơ sở đó. Việc các lập trình viên mỗi người thực hiện các "kiểu cơ sở" của riêng họ sẽ khiến việc chuyển các đối tượng giữa các mã của những người khác nhau trở nên khó khăn hơn, nhưng có một kiểu cơ sở chung mà nhiều lập trình viên thừa hưởng sẽ giúp việc này dễ dàng hơn.

ĐỊA CHỈ

Sử dụng các mẫu, có thể định nghĩa một "người giữ đối tượng tùy ý" và hỏi nó về loại đối tượng có trong đó; gói Boost chứa một thứ gọi là như vậy any. Do đó, mặc dù C ++ không có loại "tham chiếu có thể kiểm tra loại tiêu chuẩn", nhưng có thể tạo một loại. Điều này không giải quyết được vấn đề đã nói ở trên khi không có thứ gì đó trong tiêu chuẩn ngôn ngữ, nghĩa là không tương thích giữa các triển khai của các lập trình viên khác nhau, nhưng nó giải thích cách C ++ có được mà không cần loại cơ sở mà mọi thứ đều có nguồn gốc: bằng cách tạo ra nó một cái gì đó hoạt động như một.


Điều đó đã thất bại tại thời gian biên dịch trong C ++ , JavaC # .
milleniumorms

1
@milleniumorms: Nếu Wooferlà một giao diện và Catcó thể kế thừa, dàn diễn viên sẽ hợp pháp vì có thể tồn tại (nếu không phải bây giờ, có thể trong tương lai) một WoofingCatkế thừa từ Catvà thực hiện Woofer. Lưu ý rằng dưới sự biên soạn / liên kết mô hình Java, tạo ra một WoofingCatsẽ không yêu cầu truy cập vào mã nguồn cho Cathay Woofer.
18/2/2015

3
C ++ có Dynamic_cast , xử lý chính xác việc cố gắng truyền từ a Catsang a Woofervà sẽ trả lời câu hỏi "bạn có thể chuyển đổi thành loại X" không. C ++ sẽ cho phép bạn ép buộc diễn viên, vì có thể bạn thực sự biết bạn đang làm gì, nhưng nó cũng sẽ giúp bạn nếu đó không phải là điều bạn thực sự muốn làm.
Rob K

2
@RobK: Tất nhiên bạn đúng về cú pháp; mea culpa. Tôi đã đọc thêm một chút về Dynamic_cast và dường như trong một nghĩa nào đó, C ++ hiện đại có tất cả các đối tượng đa hình xuất phát từ lớp cơ sở "đối tượng đa hình" với bất kỳ trường nào là cần thiết để xác định loại đối tượng (thường là vtable con trỏ, mặc dù đó là một chi tiết thực hiện). C ++ không mô tả các lớp đa hình theo cách đó, nhưng chuyển một con trỏ tới dynamic_castsẽ có hành vi được xác định nếu nó trỏ đến một đối tượng đa hình và Hành vi không xác định nếu không, do đó, từ góc độ ngữ nghĩa ...
supercat

2
... Tất cả các đối tượng đa hình lưu trữ một số thông tin có cùng bố cục và tất cả đều hỗ trợ một hành vi không được hỗ trợ bởi các đối tượng không đa hình; đối với tôi, điều đó có nghĩa là họ hành xử như thể họ xuất phát từ một cơ sở chung, cho dù định nghĩa ngôn ngữ có sử dụng thuật ngữ đó hay không.
19/2/2015

1

Trên thực tế, Symbian C ++ có một lớp cơ sở phổ quát, CBase, cho tất cả các đối tượng hoạt động theo một cách cụ thể (chủ yếu là nếu chúng phân bổ đống). Nó cung cấp một hàm hủy ảo, làm trống bộ nhớ của lớp khi xây dựng và ẩn hàm tạo.

Lý do đằng sau là ngôn ngữ cho các hệ thống nhúng và trình biên dịch C ++ và thông số kỹ thuật thực sự rất tồi tệ 10 năm trước.

Không phải tất cả các lớp được thừa hưởng từ điều này, chỉ một số.

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.