Tại sao C ++ STL lại dựa rất nhiều vào các mẫu? (và không phải trên * giao diện *)


211

Ý tôi là, ngoài cái tên bắt buộc của nó (Thư viện mẫu tiêu chuẩn) ...

C ++ ban đầu dự định trình bày các khái niệm OOP vào C. Đó là: bạn có thể cho biết một thực thể cụ thể có thể và không thể làm gì (bất kể nó hoạt động như thế nào) dựa trên phân cấp lớp và lớp của nó. Một số bố cục của các khả năng khó mô tả theo cách này do các vấn đề của đa kế thừa và thực tế là C ++ hỗ trợ khái niệm giao diện theo cách hơi vụng về (so với java, v.v.), nhưng nó có (và có thể được cải thiện).

Và sau đó các mẫu ra đời, cùng với STL. STL dường như lấy các khái niệm OOP cổ điển và xả chúng xuống cống, thay vào đó sử dụng các mẫu.

Cần có sự phân biệt giữa các trường hợp khi các mẫu được sử dụng để khái quát các loại trong đó các loại chủ đề không liên quan đến hoạt động của mẫu (ví dụ: các thùng chứa). Có mộtvector<int> ý nghĩa hoàn hảo.

Tuy nhiên, trong nhiều trường hợp khác (iterator và thuật toán), các kiểu templated được cho là tuân theo một "khái niệm" (Iterator đầu vào, Iterator chuyển tiếp, v.v.) trong đó các chi tiết thực tế của khái niệm được xác định hoàn toàn bằng cách triển khai mẫu hàm / lớp chứ không phải theo loại của loại được sử dụng với mẫu, đây là một phần chống sử dụng OOP.

Ví dụ, bạn có thể nói với hàm:

void MyFunc(ForwardIterator<...> *I);

Cập nhật: Vì không rõ ràng trong câu hỏi ban đầu, ForwardIterator có thể tự tạo khuôn mẫu để cho phép bất kỳ loại ForwardIterator nào. Trái lại có ForwardIterator là một khái niệm.

chỉ mong đợi Bộ lặp chuyển tiếp chỉ bằng cách xem định nghĩa của nó, nơi bạn cần xem xét việc triển khai hoặc tài liệu cho:

template <typename Type> void MyFunc(Type *I);

Hai tuyên bố tôi có thể đưa ra để sử dụng các mẫu: mã được biên dịch có thể được thực hiện hiệu quả hơn, bằng cách biên dịch mẫu cho từng loại được sử dụng, thay vì sử dụng vtables. Và thực tế là các mẫu có thể được sử dụng với các kiểu bản địa.

Tuy nhiên, tôi đang tìm kiếm một lý do sâu sắc hơn tại sao lại từ bỏ OOP cổ điển để ủng hộ việc tạo khuôn mẫu cho STL? (Giả sử bạn đọc đến đó: P)


4
Bạn có thể kiểm tra stackoverflow.com/questions/31693/ . Câu trả lời được chấp nhận là một lời giải thích tuyệt vời về những gì mẫu cung cấp cho bạn qua thuốc generic.
James McMahon

6
@Jonas: Điều đó không có ý nghĩa. Các ràng buộc về bộ nhớ cache chi phí chu kỳ đồng hồ, đó là lý do tại sao nó quan trọng. Vào cuối ngày, đó là chu kỳ đồng hồ, không phải bộ đệm, xác định hiệu suất. Bộ nhớ và bộ nhớ cache chỉ quan trọng trong chừng mực nó ảnh hưởng đến chu kỳ đồng hồ đã sử dụng. Hơn nữa, thí nghiệm có thể được thực hiện dễ dàng. So sánh, giả sử, std :: for_Each được gọi với một đối số functor, với cách tiếp cận OOP / vtable tương đương. Sự khác biệt trong hiệu suất là đáng kinh ngạc . Đó là lý do tại sao phiên bản mẫu được sử dụng.
jalf

7
và không có lý do tại sao mã dự phòng sẽ lấp đầy icache. Nếu tôi khởi tạo vectơ <char> và vectơ <int> trong chương trình của mình, tại sao mã vectơ <char> phải được tải vào icache trong khi tôi xử lý vectơ <int>? Trong thực tế, mã cho vectơ <int> bị cắt bớt bởi vì nó không phải bao gồm mã để truyền, vtables và indirection.
jalf

3
Alex Stepanov giải thích lý do tại sao thừa kế và bình đẳng không chơi tốt với nhau.
dòng chảy

6
@BerndJendrissek: Uhm, thân thiết, nhưng không có chính mình. Có, chi phí mã nhiều hơn về băng thông bộ nhớ và sử dụng bộ đệm nếu nó thực sự được sử dụng . Nhưng không có lý do cụ thể để mong đợi một vector<int>vector<char>được sử dụng cùng một lúc. Họ có thể, chắc chắn, nhưng bạn có thể sử dụng bất kỳ hai đoạn mã nào cùng một lúc. Điều đó không liên quan gì đến các mẫu, C ++ hoặc STL. Không có gì trong phần khởi tạo vector<int>yêu cầu vector<char>mã được tải hoặc thực thi.
jalf

Câu trả lời:


607

Câu trả lời ngắn gọn là "vì C ++ đã chuyển sang". Vâng, trở lại vào cuối những năm 70, Stroustrup dự định tạo ra một bản C được nâng cấp với khả năng OOP, nhưng đó là một thời gian dài trước đây. Vào thời điểm ngôn ngữ được chuẩn hóa vào năm 1998, nó không còn là ngôn ngữ OOP nữa. Đó là một ngôn ngữ đa mô hình. Nó chắc chắn có một số hỗ trợ cho mã OOP, nhưng nó cũng có ngôn ngữ mẫu hoàn chỉnh được phủ lên, nó cho phép lập trình siêu dữ liệu thời gian biên dịch và mọi người đã phát hiện ra lập trình chung. Đột nhiên, OOP dường như không quan trọng lắm. Không phải khi chúng ta có thể viết mã đơn giản hơn, ngắn gọn hơn hiệu quả hơn bằng cách sử dụng các kỹ thuật có sẵn thông qua các mẫu và lập trình chung.

OOP không phải là chén thánh. Đó là một ý tưởng dễ thương, và nó đã được cải tiến khá nhiều so với các ngôn ngữ thủ tục từ những năm 70 khi nó được phát minh. Nhưng thật ra không phải tất cả đều bị bẻ khóa. Trong nhiều trường hợp, nó vụng về và dài dòng và nó không thực sự thúc đẩy mã có thể tái sử dụng hoặc mô đun hóa.

Đó là lý do tại sao cộng đồng C ++ ngày nay quan tâm nhiều hơn đến lập trình chung và tại sao mọi người cuối cùng cũng bắt đầu nhận ra rằng lập trình chức năng cũng khá thông minh. OOP tự nó không phải là một cảnh đẹp.

Hãy thử vẽ một biểu đồ phụ thuộc của STL "OOP-ified" giả định. Có bao nhiêu lớp sẽ phải biết về nhau? Sẽ có rất nhiều phụ thuộc. Bạn có thể chỉ bao gồm vectortiêu đề, mà không cần nhận iteratorhoặc thậm chí iostreamkéo vào không? STL làm cho điều này dễ dàng. Một vectơ biết về kiểu lặp mà nó định nghĩa, và đó là tất cả. Các thuật toán STL không biết . Chúng thậm chí không cần bao gồm một tiêu đề lặp, mặc dù tất cả chúng đều chấp nhận các trình vòng lặp làm tham số. Cái nào hơn mod?

STL có thể không tuân theo các quy tắc của OOP vì Java định nghĩa nó, nhưng nó không đạt được các mục tiêu của OOP? Nó không đạt được khả năng sử dụng lại, khớp nối thấp, mô đun hóa và đóng gói?

Và không phải nó đạt được những mục tiêu này tốt hơn phiên bản OOP-ified sẽ sao?

Về lý do tại sao STL được sử dụng vào ngôn ngữ, một số điều đã xảy ra dẫn đến STL.

Đầu tiên, các mẫu đã được thêm vào C ++. Chúng được thêm vào vì nhiều lý do tương tự mà thuốc generic được thêm vào .NET. Có vẻ là một ý tưởng tốt để có thể viết những thứ như "thùng chứa loại T" mà không vứt bỏ sự an toàn của loại. Tất nhiên, việc thực hiện mà họ giải quyết khá phức tạp và mạnh mẽ hơn nhiều.

Sau đó mọi người phát hiện ra rằng cơ chế mẫu mà họ đã thêm thậm chí còn mạnh hơn cả mong đợi. Và ai đó bắt đầu thử nghiệm sử dụng các mẫu để viết một thư viện chung chung hơn. Một lấy cảm hứng từ lập trình chức năng và một lấy từ tất cả các khả năng mới của C ++.

Ông đã trình bày nó với ủy ban ngôn ngữ C ++, người đã mất khá nhiều thời gian để làm quen với nó bởi vì nó trông rất lạ và khác biệt, nhưng cuối cùng nhận ra rằng nó hoạt động tốt hơn so với các tương đương OOP truyền thống mà họ phải đưa vào . Vì vậy, họ đã thực hiện một vài điều chỉnh cho nó, và chấp nhận nó vào thư viện tiêu chuẩn.

Đó không phải là một lựa chọn ý thức hệ, nó không phải là một lựa chọn chính trị "chúng ta có muốn trở thành OOP hay không", nhưng là một lựa chọn rất thực dụng. Họ đã đánh giá thư viện và thấy rằng nó hoạt động rất tốt.

Trong mọi trường hợp, cả hai lý do bạn đề cập để ủng hộ STL là hoàn toàn cần thiết.

Thư viện chuẩn C ++ được hiệu quả. Nếu nó kém hiệu quả hơn, giả sử, mã C cuộn bằng tay tương đương, thì mọi người sẽ không sử dụng nó. Điều đó sẽ làm giảm năng suất, tăng khả năng xảy ra lỗi và nói chung chỉ là một ý tưởng tồi.

Và STL để làm việc với các kiểu dữ liệu, bởi vì các loại nguyên thủy là tất cả các bạn có bằng C, và họ là một phần quan trọng trong cả hai ngôn ngữ. Nếu STL không hoạt động với mảng gốc, nó sẽ vô dụng .

Câu hỏi của bạn có một giả định mạnh mẽ rằng OOP là "tốt nhất". Tôi tò mò muốn nghe tại sao. Bạn hỏi tại sao họ "từ bỏ OOP cổ điển". Tôi đang tự hỏi tại sao họ nên bị mắc kẹt với nó. Những lợi thế nào nó sẽ có?


22
Đó là một bài viết hay, nhưng tôi muốn làm nổi bật một chi tiết. STL không phải là "sản phẩm" của C ++. Trên thực tế, STL, như một khái niệm, đã tồn tại trước C ++ và C ++ chỉ là một ngôn ngữ hiệu quả có (gần như) đủ sức mạnh để lập trình chung, vì vậy STL được viết bằng C ++.
Igor Krivokon

17
Vì các bình luận tiếp tục đưa nó lên, vâng, tôi biết rằng tên STL không rõ ràng. Nhưng tôi không thể nghĩ ra một cái tên hay hơn cho "một phần của thư viện chuẩn C ++ được mô hình hóa trên STL". Tên de-facto cho rằng một phần của thư viện chuẩn chỉ "STL", mặc dù nó là đúng chính xác. :) Miễn là mọi người không sử dụng STL làm tên cho toàn bộ thư viện tiêu chuẩn (bao gồm cả các tiêu đề IOStream và C stdlib), tôi rất vui. :)
jalf

5
@einpoklum Và chính xác bạn sẽ đạt được gì từ một lớp cơ sở trừu tượng? Lấy std::setmột ví dụ. Nó không kế thừa từ một lớp cơ sở trừu tượng. Làm thế nào mà giới hạn việc sử dụng của bạn std::set? Có bất cứ điều gì bạn không thể làm với một std::setvì nó không kế thừa từ một lớp cơ sở trừu tượng?
dòng chảy

22
@einpoklum vui lòng xem ngôn ngữ Smalltalk, mà Alan Kay đã thiết kế để trở thành ngôn ngữ OOP khi ông phát minh ra thuật ngữ OOP. Nó không có giao diện. OOP không phải là về giao diện hoặc các lớp cơ sở trừu tượng. Bạn có định nói rằng "Java, không giống với những gì người phát minh ra thuật ngữ OOP có trong đầu là OOP hơn C ++, cũng không giống với những gì người phát minh ra thuật ngữ OOP có trong tâm trí"? Điều bạn muốn nói là "C ++ không đủ giống Java theo sở thích của tôi". Điều đó là công bằng, nhưng nó không liên quan gì đến OOP.
jalf

8
@MasonWheeler nếu câu trả lời này là một mớ vô nghĩa trắng trợn bạn sẽ không thấy hàng trăm nhà phát triển trên toàn thế giới bỏ phiếu +1 về điều này chỉ với ba người làm khác
panda-34

88

Câu trả lời trực tiếp nhất cho những gì tôi nghĩ bạn đang hỏi / phàn nàn là đây: Giả định rằng C ++ là ngôn ngữ OOP là một giả định sai.

C ++ là một ngôn ngữ đa mô hình. Nó có thể được lập trình bằng các nguyên tắc OOP, nó có thể được lập trình theo thủ tục, nó có thể được lập trình một cách tổng quát (các mẫu) và với C ++ 11 (trước đây gọi là C ++ 0x) một số thứ thậm chí có thể được lập trình theo chức năng.

Các nhà thiết kế của C ++ coi đây là một lợi thế, vì vậy họ sẽ lập luận rằng việc hạn chế C ++ hoạt động như một ngôn ngữ OOP thuần túy khi lập trình chung giải quyết vấn đề tốt hơn và, nói chung , sẽ là một bước lùi.


4
"Và với C ++ 0x, một số thứ thậm chí có thể được lập trình theo chức năng" - nó có thể được lập trình theo chức năng mà không cần các tính năng đó, chỉ đơn giản hơn.
Jonas Kölker

3
@Tyler Thật vậy, nếu bạn hạn chế C ++ thành OOP thuần túy, bạn sẽ bị bỏ lại với Objective-C.
Justicle

@TylerMcHenry: Vừa mới hỏi điều này , tôi thấy tôi vừa mới thốt ra câu trả lời giống như bạn! Chỉ cần một điểm. Tôi ước bạn có thể thêm một thực tế là Thư viện tiêu chuẩn không thể được sử dụng để viết mã Hướng đối tượng.
einpoklum

74

Sự hiểu biết của tôi là Stroustrup ban đầu thích thiết kế container "theo kiểu OOP" và trên thực tế không thấy cách nào khác để làm điều đó. Alexander Stepanov là người chịu trách nhiệm về STL và các mục tiêu của ông không bao gồm "làm cho nó hướng đối tượng" :

Đó là điểm cơ bản: các thuật toán được xác định trên các cấu trúc đại số. Phải mất vài năm nữa tôi mới nhận ra rằng bạn phải mở rộng khái niệm về cấu trúc bằng cách thêm các yêu cầu phức tạp vào các tiên đề thông thường. ... Tôi tin rằng các lý thuyết lặp là trung tâm của Khoa học Máy tính vì các lý thuyết về các vòng hoặc không gian Banach là trung tâm của Toán học. Mỗi lần tôi nhìn vào một thuật toán tôi sẽ cố gắng tìm một cấu trúc mà nó được định nghĩa. Vì vậy, những gì tôi muốn làm là mô tả các thuật toán một cách khái quát. Đó là những gì tôi muốn làm. Tôi có thể dành một tháng để làm việc với một thuật toán nổi tiếng đang cố gắng tìm đại diện chung của nó. ...

STL, ít nhất là đối với tôi, đại diện cho cách lập trình duy nhất là có thể. Thật vậy, nó hoàn toàn khác với lập trình C ++ như đã được trình bày và vẫn được trình bày trong hầu hết các sách giáo khoa. Nhưng, bạn thấy đấy, tôi đã không cố gắng lập trình trong C ++, tôi đã cố gắng tìm đúng cách để đối phó với phần mềm. ...

Tôi đã có nhiều khởi đầu sai. Ví dụ, tôi đã dành nhiều năm cố gắng tìm một số sử dụng cho thừa kế và ảo, trước khi tôi hiểu tại sao cơ chế đó bị lỗi cơ bản và không nên được sử dụng. Tôi rất vui vì không ai có thể nhìn thấy tất cả các bước trung gian - hầu hết chúng đều rất ngớ ngẩn.

(Anh ấy giải thích lý do tại sao kế thừa và ảo - hay còn gọi là thiết kế hướng đối tượng "về cơ bản là thiếu sót và không nên được sử dụng" trong phần còn lại của cuộc phỏng vấn).

Khi Stepanov trình bày thư viện của mình cho Stroustrup, Stroustrup và những người khác đã trải qua những nỗ lực phi thường để đưa nó vào tiêu chuẩn ISO C ++ (cùng một cuộc phỏng vấn):

Sự hỗ trợ của Bjarne Stroustrup là rất quan trọng. Bjarne thực sự muốn STL theo tiêu chuẩn và nếu Bjarne muốn thứ gì đó, anh ta sẽ có được nó. ... Anh ấy thậm chí còn bắt tôi phải thay đổi STL mà tôi sẽ không bao giờ thực hiện cho bất kỳ ai khác ... anh ấy là người có đầu óc đơn lẻ nhất mà tôi biết. Anh ấy đã hoàn thành công việc. Phải mất một thời gian anh ta mới hiểu STL là gì, nhưng khi anh ta làm vậy, anh ta đã sẵn sàng để đẩy nó qua. Ông cũng đóng góp cho STL bằng cách đứng lên cho rằng hơn một cách lập trình là hợp lệ - chống lại sự kết thúc và cường điệu trong hơn một thập kỷ, và theo đuổi sự kết hợp giữa tính linh hoạt, hiệu quả, quá tải và an toàn kiểu các mẫu đã tạo STL có thể. Tôi muốn nói rõ rằng Bjarne là nhà thiết kế ngôn ngữ ưu việt trong thế hệ của tôi.


2
Phỏng vấn thú vị. Khá chắc chắn rằng tôi đã đọc nó trước đây một thời gian, nhưng chắc chắn là đáng để xem lại. :)
jalf

3
Một trong những cuộc phỏng vấn thú vị nhất về lập trình mà tôi từng đọc. Mặc dù nó khiến tôi khao khát biết thêm chi tiết ...
Felixyz

Rất nhiều khiếu nại mà anh ta đưa ra về các ngôn ngữ như Java ("Bạn không thể viết một max () chung trong Java có hai đối số thuộc loại nào đó và có giá trị trả về cùng loại đó") chỉ liên quan đến các phiên bản rất sớm của ngôn ngữ, trước khi thuốc generic được thêm vào. Ngay từ đầu, người ta đã biết rằng thuốc generic cuối cùng sẽ được thêm vào, mặc dù (một khi một cú pháp / ngữ nghĩa khả thi đã được tìm ra), vì vậy những lời chỉ trích của ông chủ yếu là vô căn cứ. Đúng, thuốc generic trong một số hình thức là cần thiết để duy trì sự an toàn của loại hình trong một ngôn ngữ được gõ tĩnh, nhưng không, điều đó không làm cho OO trở nên vô dụng.
Một số chàng trai

1
@SomeGuy Họ không phàn nàn về Java mỗi lần. Anh ấy đang nói về lập trình OO "tiêu chuẩn" của SmallTalk hoặc, nói, Java ". Cuộc phỏng vấn là từ cuối những năm 90 (anh ấy đề cập đến làm việc tại SGI, mà anh ấy đã rời đi vào năm 2000 để làm việc tại AT & T). Generics chỉ được thêm vào Java vào năm 2004 trong phiên bản 1.5 và chúng là một sai lệch so với mô hình OO "tiêu chuẩn".
melpomene

24

Câu trả lời được tìm thấy trong cuộc phỏng vấn này với Stepanov, tác giả của STL:

Đúng. STL không hướng đối tượng. Tôi nghĩ rằng tính hướng đối tượng gần như là một trò lừa bịp như Trí tuệ nhân tạo. Tôi vẫn chưa thấy một đoạn mã thú vị đến từ những người OO này.


Đá quý đẹp; Bạn có biết nó đến từ năm nào không?
Kos

2
@Kos, theo web.archive.org/web/20000607205939/http://www.stlport.org/... phiên bản đầu tiên của trang liên kết là từ ngày 07 tháng 6 năm 2001. Các trang đó ở phía dưới nói Copyright 2001- 2008.
alfC

@Kos Stepanov đề cập đến làm việc tại SGI trong câu trả lời đầu tiên. Anh ấy rời SGI vào tháng 5 năm 2000, vì vậy có lẽ cuộc phỏng vấn cũ hơn thế.
melpomene

18

Tại sao một thiết kế OOP thuần túy cho Thư viện Thuật toán & Cấu trúc dữ liệu sẽ tốt hơn?! OOP không phải là giải pháp cho mọi thứ.

IMHO, STL là thư viện thanh lịch nhất mà tôi từng thấy :)

cho câu hỏi của bạn,

bạn không cần đa hình thời gian chạy, đó thực sự là một lợi thế cho STL để triển khai Thư viện bằng đa hình tĩnh, điều đó có nghĩa là hiệu quả. Cố gắng viết một Sắp xếp chung hoặc Khoảng cách hoặc thuật toán bao giờ áp dụng cho TẤT CẢ các thùng chứa! Sắp xếp trong Java của bạn sẽ gọi các hàm động thông qua các cấp n để được thực thi!

Bạn cần những thứ ngu ngốc như Boxing và Unboxing để che giấu những giả định khó chịu của cái gọi là ngôn ngữ Pure OOP.

Vấn đề duy nhất tôi thấy với STL và các mẫu nói chung là các thông báo lỗi khủng khiếp. Điều này sẽ được giải quyết bằng cách sử dụng các khái niệm trong C ++ 0X.

So sánh STL với Bộ sưu tập trong Java cũng giống như so sánh Taj Mahal với nhà tôi :)


12
Cái gì, Taj Mahal nhỏ và thanh lịch, và ngôi nhà của bạn có kích thước của một ngọn núi, và một mớ hỗn độn? ;)
jalf

Các khái niệm không còn là một phần của c ++ 0x nữa. Một số thông báo lỗi có thể được xử lý trước bằng cách sử dụng static_assertcó lẽ.
KitsuneYMG

GCC 4.6 đã cải thiện các thông báo lỗi mẫu và tôi tin rằng 4.7+ thậm chí còn tốt hơn với nó.
David Stone

Một khái niệm về cơ bản là "giao diện" mà OP đã yêu cầu. Sự khác biệt duy nhất là "sự kế thừa" từ một Khái niệm là ẩn (nếu một lớp có tất cả các hàm thành viên đúng, nó sẽ tự động là một kiểu con của Khái niệm) chứ không phải rõ ràng (một lớp Java phải tuyên bố rõ ràng rằng nó thực hiện một giao diện) . Tuy nhiên, cả phân nhóm ngầm định và rõ ràng đều là OO hợp lệ và một số ngôn ngữ OO có sự kế thừa ngầm hoạt động giống như các khái niệm. Vì vậy, những gì đang được nói ở đây về cơ bản là "OO hút: sử dụng các mẫu. Nhưng các mẫu có vấn đề, vì vậy hãy sử dụng các khái niệm (đó là OO)."
Một số chàng trai

11

Các kiểu templated được cho là tuân theo một "khái niệm" (Iterator đầu vào, Iterator chuyển tiếp, v.v.) trong đó các chi tiết thực tế của khái niệm được xác định hoàn toàn bởi việc thực hiện hàm / lớp mẫu chứ không phải bởi lớp của loại được sử dụng với mẫu, một phần chống sử dụng OOP.

Tôi nghĩ rằng bạn hiểu sai mục đích sử dụng các khái niệm theo mẫu. Chuyển tiếp Iterator, ví dụ, là một khái niệm rất rõ ràng. Để tìm các biểu thức phải hợp lệ để một lớp trở thành Bộ lặp chuyển tiếp và ngữ nghĩa của chúng bao gồm độ phức tạp tính toán, bạn xem tiêu chuẩn hoặc tại http://www.sgi.com/tech/stl/ForwardIterator.html (bạn phải theo các liên kết đến Đầu vào, Đầu ra và Bộ lặp Trivial để xem tất cả).

Tài liệu đó là một giao diện hoàn toàn tốt và "các chi tiết thực tế của khái niệm" được định nghĩa ngay tại đó. Chúng không được xác định bởi các triển khai của Bộ lặp chuyển tiếp và chúng cũng không được xác định bởi các thuật toán sử dụng Bộ lặp chuyển tiếp.

Sự khác biệt về cách các giao diện được xử lý giữa STL và Java là ba lần:

1) STL định nghĩa các biểu thức hợp lệ bằng cách sử dụng đối tượng, trong khi Java định nghĩa các phương thức phải có thể gọi được trên đối tượng. Tất nhiên một biểu thức hợp lệ có thể là một cuộc gọi phương thức (hàm thành viên), nhưng nó không phải như vậy.

2) Giao diện Java là các đối tượng thời gian chạy, trong khi các khái niệm STL không thể nhìn thấy trong thời gian chạy ngay cả với RTTI.

3) Nếu bạn không tạo ra các biểu thức hợp lệ bắt buộc cho một khái niệm STL, bạn sẽ gặp một lỗi biên dịch không xác định khi bạn khởi tạo một số mẫu với loại. Nếu bạn không thực hiện một phương thức cần thiết của giao diện Java, bạn sẽ gặp một lỗi biên dịch cụ thể.

Phần thứ ba này là nếu bạn thích một kiểu "gõ vịt" (thời gian biên dịch): các giao diện có thể được ẩn. Trong Java, các giao diện có phần rõ ràng: một lớp "là" Có thể lặp lại khi và chỉ khi nó nói nó thực hiện Iterable. Trình biên dịch có thể kiểm tra xem các chữ ký của các phương thức của nó có hiện diện và đúng hay không, nhưng ngữ nghĩa vẫn ẩn (nghĩa là chúng có tài liệu hay không, nhưng chỉ có nhiều mã hơn (kiểm tra đơn vị) có thể cho bạn biết việc triển khai có đúng không).

Trong C ++, giống như trong Python, cả ngữ nghĩa và cú pháp đều ẩn, mặc dù trong C ++ (và trong Python nếu bạn có bộ tiền xử lý gõ mạnh), bạn sẽ nhận được một số trợ giúp từ trình biên dịch. Nếu một lập trình viên yêu cầu khai báo giao diện rõ ràng giống như Java của lớp triển khai, thì cách tiếp cận tiêu chuẩn là sử dụng các đặc điểm kiểu (và nhiều kế thừa có thể ngăn điều này quá dài dòng). Cái còn thiếu, so với Java, là một khuôn mẫu duy nhất mà tôi có thể khởi tạo với kiểu của mình và nó sẽ biên dịch khi và chỉ khi tất cả các biểu thức cần thiết là hợp lệ cho kiểu của tôi. Điều này sẽ cho tôi biết liệu tôi đã thực hiện tất cả các bit cần thiết chưa, "trước khi tôi sử dụng nó". Đó là một sự tiện lợi, nhưng nó không phải là cốt lõi của OOP (và nó vẫn không kiểm tra ngữ nghĩa,

STL có thể hoặc không đủ OO theo sở thích của bạn, nhưng chắc chắn nó tách biệt giao diện sạch với việc thực hiện. Nó không có khả năng của Java để phản ánh các giao diện và nó báo cáo các vi phạm các yêu cầu giao diện khác nhau.

bạn có thể cho biết chức năng ... chỉ mong đợi Bộ lặp chuyển tiếp chỉ bằng cách xem định nghĩa của nó, nơi bạn cần xem xét việc triển khai hoặc tài liệu cho ...

Cá nhân tôi nghĩ rằng các loại ngầm là một thế mạnh, khi được sử dụng một cách thích hợp. Thuật toán cho biết những gì nó làm với các tham số mẫu của nó và người triển khai đảm bảo những điều đó hoạt động: đó chính xác là mẫu số chung của "giao diện" nên làm gì. Hơn nữa, với STL, bạn không thể sử dụng, std::copydựa trên việc tìm khai báo chuyển tiếp của nó trong tệp tiêu đề. Các lập trình viên nên tìm ra những gì một chức năng dựa trên tài liệu của nó, không chỉ dựa trên chữ ký của hàm. Điều này đúng trong C ++, Python hoặc Java. Có những hạn chế về những gì có thể đạt được khi gõ bằng bất kỳ ngôn ngữ nào và cố gắng sử dụng gõ để làm điều gì đó không làm (kiểm tra ngữ nghĩa) sẽ là một lỗi.

Điều đó nói rằng, các thuật toán STL thường đặt tên cho các tham số mẫu của chúng theo cách làm cho nó rõ ràng khái niệm nào là bắt buộc. Tuy nhiên, điều này là để cung cấp thêm thông tin hữu ích trong dòng đầu tiên của tài liệu, không phải để khai báo chuyển tiếp nhiều thông tin hơn. Có nhiều điều bạn cần biết hơn có thể được gói gọn trong các loại tham số, vì vậy bạn phải đọc tài liệu. (Ví dụ: trong các thuật toán có phạm vi đầu vào và bộ lặp đầu ra, rất có thể bộ lặp đầu ra cần đủ "khoảng trống" cho một số lượng đầu ra nhất định dựa trên kích thước của phạm vi đầu vào và có thể là các giá trị trong đó. Hãy thử gõ mạnh vào đó. )

Đây là Bjarne trên các giao diện được khai báo rõ ràng: http://www.artima.com/cppsource/cpp0xP.html

Trong khái quát, một đối số phải là của một lớp xuất phát từ một giao diện (C ++ tương đương với giao diện là lớp trừu tượng) được chỉ định trong định nghĩa của chung. Điều đó có nghĩa là tất cả các loại đối số chung phải phù hợp với một hệ thống phân cấp. Điều đó đặt ra những ràng buộc không cần thiết đối với các thiết kế đòi hỏi tầm nhìn xa không hợp lý về phía các nhà phát triển. Ví dụ: nếu bạn viết chung chung và tôi xác định một lớp, mọi người không thể sử dụng lớp của tôi làm đối số cho chung của bạn trừ khi tôi biết về giao diện bạn đã chỉ định và đã bắt nguồn lớp của tôi từ đó. Đó là cứng nhắc.

Nhìn theo một cách khác, với cách gõ vịt bạn có thể thực hiện một giao diện mà không biết rằng giao diện đó tồn tại. Hoặc ai đó có thể viết một giao diện có chủ ý sao cho lớp của bạn thực hiện nó, đã tham khảo tài liệu của bạn để thấy rằng họ không yêu cầu bất cứ điều gì bạn chưa làm. Đó là linh hoạt.


Trên các giao diện được khai báo rõ ràng, hai từ: loại lớp. (Đó là những gì Stepanov có nghĩa là "khái niệm".)
pyon

"Nếu bạn không tạo ra các biểu thức hợp lệ bắt buộc cho một khái niệm STL, bạn sẽ gặp một lỗi biên dịch không xác định khi bạn khởi tạo một số mẫu với loại." -- điều đó là sai. Chuyển vào một cái gì đó đến stdthư viện không phù hợp với một khái niệm thường là "không đúng định dạng, không cần chẩn đoán".
Yakk - Adam Nevraumont

Đúng, tôi đã chơi nhanh và lỏng lẻo với thuật ngữ "hợp lệ". Tôi chỉ có nghĩa là nếu trình biên dịch không thể biên dịch một trong các biểu thức cần thiết, thì nó sẽ báo cáo một cái gì đó.
Steve Jessop

8

"OOP với tôi có nghĩa là chỉ nhắn tin, duy trì và bảo vệ cục bộ và che giấu quá trình nhà nước, và ràng buộc cực kỳ muộn của tất cả mọi thứ. Nó có thể được thực hiện trong Smalltalk và LISP. Có thể có các hệ thống khác trong đó có thể, nhưng có thể Tôi không biết về họ. " - Alan Kay, người tạo ra Smalltalk.

C ++, Java và hầu hết các ngôn ngữ khác đều khá xa so với OOP cổ điển. Điều đó nói rằng, tranh luận về ý thức hệ không phải là hiệu quả khủng khiếp. C ++ không thuần túy theo bất kỳ ý nghĩa nào, vì vậy nó thực hiện chức năng dường như có ý nghĩa thực dụng tại thời điểm đó.


7

STL bắt đầu với ý định cung cấp một thư viện lớn bao gồm thuật toán được sử dụng phổ biến nhất - với mục tiêu là hành vi và hiệu suất của sự đồng ý . Mẫu xuất hiện như một yếu tố quan trọng để thực hiện mục tiêu đó và khả thi.

Chỉ để cung cấp một tài liệu tham khảo khác:

Al Stevens Phỏng vấn Alex Stepanov, vào tháng 3 năm 1995 của DDJ:

Stepanov giải thích kinh nghiệm làm việc và lựa chọn của mình đối với một thư viện thuật toán lớn, cuối cùng phát triển thành STL.

Hãy cho chúng tôi biết vài điều về mối quan tâm lâu dài của bạn đối với lập trình chung

..... Sau đó, tôi được mời làm việc tại Phòng thí nghiệm Bell làm việc trong nhóm C ++ trên các thư viện C ++. Họ hỏi tôi có thể làm điều đó trong C ++ không. Tất nhiên, tôi không biết C ++ và dĩ nhiên, tôi nói tôi có thể. Nhưng tôi không thể làm điều đó trong C ++, bởi vì vào năm 1987, C ++ không có các mẫu, điều này rất cần thiết để cho phép phong cách lập trình này. Kế thừa là cơ chế duy nhất để có được sự hào phóng và nó không đủ.

Ngay cả bây giờ kế thừa C ++ không được sử dụng nhiều cho lập trình chung. Hãy thảo luận tại sao. Nhiều người đã cố gắng sử dụng tính kế thừa để thực hiện các cấu trúc dữ liệu và các lớp container. Như chúng ta biết bây giờ, có rất ít nếu có bất kỳ nỗ lực thành công nào. Kế thừa C ++ và phong cách lập trình liên quan đến nó bị hạn chế đáng kể. Không thể thực hiện một thiết kế bao gồm một thứ tầm thường như bình đẳng sử dụng nó. Nếu bạn bắt đầu với một lớp cơ sở X ở gốc của hệ thống phân cấp của bạn và xác định một toán tử đẳng thức ảo trên lớp này, lấy một đối số của loại X, thì lấy lớp Y từ lớp X. Giao diện của đẳng thức là gì? Nó có sự bình đẳng so sánh Y với X. Sử dụng động vật làm ví dụ (người OO yêu động vật), xác định động vật có vú và lấy được hươu cao cổ từ động vật có vú. Sau đó xác định một người bạn đời chức năng, nơi động vật giao phối với động vật và trả lại một con vật. Sau đó, bạn lấy được con hươu cao cổ từ động vật và, tất nhiên, nó có một người bạn đời có chức năng nơi con hươu cao cổ giao phối với động vật và trả lại một con vật. Đó chắc chắn không phải là những gì bạn muốn. Trong khi giao phối có thể không quan trọng lắm đối với các lập trình viên C ++, sự bình đẳng là vậy. Tôi không biết một thuật toán duy nhất mà sự bình đẳng của một số loại không được sử dụng.


5

Vấn đề cơ bản với

void MyFunc(ForwardIterator *I);

Làm thế nào để bạn có được kiểu của thứ lặp đi lặp lại một cách an toàn? Với các mẫu, điều này được thực hiện cho bạn tại thời gian biên dịch.


1
Vâng, tôi cũng vậy: 1. Đừng cố lấy nó, vì tôi đang viết mã chung. Hoặc, 2. Nhận nó bằng bất kỳ cơ chế phản chiếu nào C ++ cung cấp những ngày này.
einpoklum

2

Hiện tại, chúng ta hãy nghĩ về thư viện tiêu chuẩn về cơ bản là một cơ sở dữ liệu của các bộ sưu tập và thuật toán.

Nếu bạn đã nghiên cứu lịch sử của cơ sở dữ liệu, chắc chắn bạn sẽ biết rằng ngay từ đầu, cơ sở dữ liệu chủ yếu là "phân cấp". Cơ sở dữ liệu phân cấp tương ứng rất chặt chẽ với OOP cổ điển - cụ thể là giống thừa kế đơn, như được sử dụng bởi Smalltalk.

Theo thời gian, rõ ràng là cơ sở dữ liệu phân cấp có thể được sử dụng để mô hình hóa hầu hết mọi thứ, nhưng trong một số trường hợp, mô hình thừa kế đơn lẻ khá hạn chế. Nếu bạn có một cánh cửa gỗ, thật tiện dụng để có thể nhìn nó như một cánh cửa, hoặc là một mảnh của một số nguyên liệu thô (thép, gỗ, v.v.)

Vì vậy, họ đã phát minh ra cơ sở dữ liệu mô hình mạng. Cơ sở dữ liệu mô hình mạng tương ứng rất chặt chẽ với nhiều kế thừa. C ++ hỗ trợ hoàn toàn nhiều kế thừa, trong khi Java hỗ trợ một hình thức giới hạn (bạn có thể kế thừa chỉ từ một lớp, nhưng cũng có thể thực hiện nhiều giao diện như bạn muốn).

Cả cơ sở dữ liệu mô hình phân cấp và mô hình mạng hầu hết đều bị mờ dần khỏi mục đích sử dụng chung (mặc dù một số ít vẫn còn trong các hốc khá cụ thể). Đối với hầu hết các mục đích, chúng đã được thay thế bởi cơ sở dữ liệu quan hệ.

Phần lớn lý do cơ sở dữ liệu quan hệ chiếm lĩnh là tính linh hoạt. Mô hình quan hệ về mặt chức năng là một siêu bộ của mô hình mạng (lần lượt là một siêu bộ của mô hình phân cấp).

C ++ đã đi theo con đường tương tự. Sự tương ứng giữa kế thừa đơn và mô hình phân cấp và giữa nhiều kế thừa và mô hình mạng là khá rõ ràng. Sự tương ứng giữa các mẫu C ++ và mô hình phân cấp có thể ít rõ ràng hơn, nhưng dù sao nó cũng khá phù hợp.

Tôi chưa thấy một bằng chứng chính thức nào về nó, nhưng tôi tin rằng các khả năng của các mẫu là một siêu bộ của những mẫu được cung cấp bởi nhiều thừa kế (rõ ràng là một siêu thay thế của một phần thừa). Một phần khó khăn là các mẫu chủ yếu bị ràng buộc tĩnh - nghĩa là, tất cả các ràng buộc xảy ra vào thời gian biên dịch, không phải thời gian chạy. Như vậy, một bằng chứng chính thức rằng thừa kế cung cấp một siêu năng lực về khả năng thừa kế có thể hơi khó khăn và phức tạp (hoặc thậm chí có thể là không thể).

Trong mọi trường hợp, tôi nghĩ đó hầu hết là lý do thực sự khiến C ++ không sử dụng tính kế thừa cho các thùng chứa của nó - không có lý do thực sự nào để làm như vậy, vì tính kế thừa chỉ cung cấp một tập hợp con các khả năng được cung cấp bởi các mẫu. Vì các mẫu về cơ bản là cần thiết trong một số trường hợp, chúng cũng có thể được sử dụng gần như ở mọi nơi.


0

Làm thế nào để bạn so sánh với ForwardIterator *? Đó là, làm thế nào để bạn kiểm tra xem mặt hàng bạn có là thứ bạn đang tìm kiếm hay bạn đã vượt qua nó?

Hầu hết thời gian, tôi sẽ sử dụng một cái gì đó như thế này:

void MyFunc(ForwardIterator<MyType>& i)

điều đó có nghĩa là tôi biết rằng tôi đang chỉ đến MyType và tôi biết cách so sánh chúng. Mặc dù trông giống như một mẫu, nhưng nó không thực sự (không có từ khóa "mẫu").


bạn chỉ có thể sử dụng các toán tử <,> và = và không biết đó là gì (mặc dù điều này có thể không phải là ý bạn)
lhahne

Tùy thuộc vào ngữ cảnh, chúng có thể không có ý nghĩa gì, hoặc chúng có thể hoạt động tốt. Thật khó để nói mà không biết thêm về MyType, mà người dùng có thể đoán được, và chúng tôi thì không.
Tanktalus

0

Câu hỏi này có nhiều câu trả lời tuyệt vời. Cũng cần đề cập rằng các mẫu hỗ trợ một thiết kế mở. Với trạng thái hiện tại của các ngôn ngữ lập trình hướng đối tượng, người ta phải sử dụng mẫu khách truy cập khi xử lý các vấn đề như vậy và OOP thực sự sẽ hỗ trợ nhiều ràng buộc động. Xem Đa phương thức mở cho C ++, P. Pirkelbauer, et.al. để đọc rất xen kẽ.

Một điểm thú vị khác của các mẫu là chúng cũng có thể được sử dụng cho đa hình thời gian chạy. Ví dụ

template<class Value,class T>
Value euler_fwd(size_t N,double t_0,double t_end,Value y_0,const T& func)
    {
    auto dt=(t_end-t_0)/N;
    for(size_t k=0;k<N;++k)
        {y_0+=func(t_0 + k*dt,y_0)*dt;}
    return y_0;
    }

Lưu ý rằng hàm này cũng sẽ hoạt động nếu Valuelà một loại vectơ nào đó ( không phải std :: vector, nên được gọi std::dynamic_arrayđể tránh nhầm lẫn)

Nếu funcnhỏ, chức năng này sẽ đạt được rất nhiều từ nội tuyến. Ví dụ sử dụng

auto result=euler_fwd(10000,0.0,1.0,1.0,[](double x,double y)
    {return y;});

Trong trường hợp này, bạn nên biết câu trả lời chính xác (2.718 ...), nhưng thật dễ dàng để xây dựng một ODE đơn giản mà không cần giải pháp cơ bản (Gợi ý: sử dụng đa thức trong y).

Bây giờ, bạn có một biểu thức lớn funcvà bạn sử dụng bộ giải ODE ở nhiều nơi, do đó, tệp thực thi của bạn bị ô nhiễm với các bản dựng mẫu ở khắp mọi nơi. Phải làm sao? Điều đầu tiên cần chú ý là một con trỏ hàm thông thường hoạt động. Sau đó, bạn muốn thêm currying để bạn viết một giao diện và một khởi tạo rõ ràng

class OdeFunction
    {
    public:
        virtual double operator()(double t,double y) const=0;
    };

template
double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func);

Nhưng phần khởi tạo ở trên chỉ hoạt động double, tại sao không viết giao diện dưới dạng mẫu:

template<class Value=double>
class OdeFunction
    {
    public:
        virtual Value operator()(double t,const Value& y) const=0;
    };

và chuyên cho một số loại giá trị phổ biến:

template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction<double>& func);

template vec4_t<double> euler_fwd(size_t N,double t_0,double t_end,vec4_t<double> y_0,const OdeFunction< vec4_t<double> >& func); // (Native AVX vector with four components)

template vec8_t<float> euler_fwd(size_t N,double t_0,double t_end,vec8_t<float> y_0,const OdeFunction< vec8_t<float> >& func); // (Native AVX vector with 8 components)

template Vector<double> euler_fwd(size_t N,double t_0,double t_end,Vector<double> y_0,const OdeFunction< Vector<double> >& func); // (A N-dimensional real vector, *not* `std::vector`, see above)

Nếu chức năng đã được thiết kế xung quanh một giao diện trước tiên, thì bạn sẽ buộc phải kế thừa từ ABC đó. Bây giờ bạn có tùy chọn này, cũng như con trỏ hàm, lambda hoặc bất kỳ đối tượng hàm nào khác. Chìa khóa ở đây là chúng ta phải có operator()()và chúng ta phải có khả năng sử dụng một số toán tử số học trên kiểu trả về của nó. Do đó, máy móc mẫu sẽ bị hỏng trong trường hợp này nếu C ++ không có quá tải toán tử.


-1

Khái niệm tách giao diện khỏi giao diện và có thể trao đổi các triển khai không phải là bản chất đối với Lập trình hướng đối tượng. Tôi tin rằng đó là một ý tưởng đã được ấp ủ trong Phát triển dựa trên thành phần như Microsoft COM. (Xem câu trả lời của tôi về Phát triển dựa trên thành phần là gì?) Lớn lên và học hỏi C ++, mọi người đã bị thổi phồng sự kế thừa và đa hình. Mãi đến những năm 90, mọi người mới bắt đầu nói "Chương trình cho một 'giao diện', không phải là một 'triển khai'" và "Thành phần đối tượng 'ưu tiên' hơn 'kế thừa lớp'." (cả hai đều được trích dẫn từ GoF bằng cách này).

Sau đó, Java xuất hiện cùng với trình thu gom rác và interfacetừ khóa tích hợp, và thật bất ngờ, nó trở nên thiết thực để thực sự tách biệt giao diện và thực hiện. Trước khi bạn biết nó, ý tưởng đã trở thành một phần của OO. C ++, các mẫu và STL có trước tất cả những điều này.


Đồng ý rằng các giao diện không chỉ là OO. Nhưng khả năng đa hình trong hệ thống loại là (nó đã có trong Simula trong thập niên 60). Các giao diện mô-đun tồn tại trong Modula-2 và Ada, nhưng chúng hoạt động trong hệ thống loại khác theo tôi nghĩ.
andygavin
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.