Чому C ++ STL так сильно базується на шаблонах? (а не на * інтерфейсах *)


211

Я маю на увазі, окрім обов'язкової назви (бібліотека стандартних шаблонів) ...

Спочатку C ++ мав на меті представити поняття OOP в C. Тобто: ви могли сказати, що конкретна сутність може і чого не може робити (незалежно від того, як це робить), грунтуючись на її ієрархії класів і класів. Деякі композиції здібностей важче описати таким чином через проблематику багаторазового успадкування та те, що C ++ підтримує концепцію інтерфейсів дещо незграбно (порівняно з java тощо), але воно є (і може бути вдосконалено).

А потім шаблони почали грати разом із STL. STL, здавалося, приймає класичні концепції OOP і скидає їх у стік, використовуючи натомість шаблони.

Повинно бути різницею випадків, коли шаблони використовуються для узагальнення типів, коли самі теми типів не мають значення для роботи шаблону (контейнери, наприклад). Мати vector<int>досконалий сенс.

Однак у багатьох інших випадках (ітератори та алгоритми) шаблонні типи повинні слідувати "концепції" (вхідний ітератор, перехідний ітератор тощо), де фактичні деталі концепції повністю визначені реалізацією шаблону функція / клас, а не за класом типу, що використовується з шаблоном, що є дещо антивикористанням OOP.

Наприклад, ви можете сказати функцію:

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

Оновлення: Як це було незрозуміло в оригінальному запитанні, ForwardIterator в порядку, щоб його шаблонувати, щоб дозволити будь-який тип ForwardIterator. Навпаки, існує ForwardIterator як концепція.

очікує, що форвард-ітератор лише дивиться на його визначення, де вам потрібно буде переглянути або реалізацію або документацію для:

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

Дві претензії, які я можу висловити на користь використання шаблонів: складений код можна зробити більш ефективним, пошивши компіляцію шаблону для кожного використовуваного типу, замість використання vtables. І те, що шаблонами можна користуватися з рідними типами.

Однак я шукаю більш глибоку причину, чому відмовитися від класичного OOP на користь шаблону для STL? (Якщо припустити, що ви читали так далеко: P)


4
Ви можете перевірити stackoverflow.com/questions/31693/… . Прийнята відповідь - це відмінне пояснення того, що шаблони пропонують вам над загальними.
Джеймс Макмахон

6
@Jonas: Це не має сенсу. Обмеження в кеші коштує тактових циклів, тому це важливо. Зрештою, продуктивність визначає саме цикли годин, а не кеш-пам'ять. Пам'ять і кеш важливі лише в тому випадку, коли це впливає на витрачені цикли годин. Більше того, експеримент можна зробити легко. Порівняйте, скажімо, std :: for_Each, викликаний аргументом функтора, з еквівалентним підходом OOP / vtable. Різниця в продуктивності є приголомшливою . Ось чому використовується версія шаблону.
jalf

7
і немає жодної причини, щоб надлишковий код заповнив ікаче. Якщо я інстанціюю вектор <char> і вектор <int> у своїй програмі, навіщо код вектор <char> завантажуватись у icache, коли я обробляю вектор <int>? Насправді, код для vector <int> оброблений, оскільки він не повинен включати код для кастингу, vtables та непрямості.
jalf

3
Олексій Степанов пояснює, чому спадкування та рівність не грають добре разом.
fredoverflow

6
@BerndJendrissek: Ум, близько, але нікого. Так, більше витрат коду з точки зору пропускної здатності пам’яті та використання кешу, якщо він взагалі використовується . Але немає особливих підстав очікувати vector<int>і vector<char>будуть використовуватися одночасно. Вони можуть, звичайно, але ви можете використовувати будь-які два фрагменти коду одночасно. Це не має нічого спільного з шаблонами, C ++ або STL. Немає нічого, в екземплярі vector<int>якого потрібно vector<char>завантажити або виконати код.
jalf

Відповіді:


607

Коротка відповідь - «оскільки C ++ перейшов». Так, ще в кінці 70-х Stroustrup мав намір створити оновлений C з можливостями OOP, але це вже давно. На той момент, коли мова була стандартизована в 1998 році, вона вже не була мовою ООП. Це була багатомовна парадигма. Він, безумовно, мав деяку підтримку коду OOP, але він також мав накладений повний термін мови шаблонів, він дозволяв метапрограмувати час компіляції, і люди виявили загальне програмування. Раптом OOP просто не здавався таким важливим. Не тоді, коли ми можемо писати простіший, більш стислий та ефективніший код, використовуючи методи, доступні через шаблони та загальне програмування.

ООП - це не святий грааль. Це симпатична ідея, і це було вдосконалення щодо процедурних мов ще в 70-х, коли вона була винайдена. Але, чесно кажучи, не все так зламано. У багатьох випадках він незграбний і багатослівний, і він насправді не сприяє багаторазовому використанню коду або модульності.

Ось чому спільнота C ++ сьогодні набагато більше зацікавлена ​​в загальному програмуванні, і тому всі нарешті починають розуміти, що функціональне програмування також досить розумне. OOP самостійно просто не дуже видовище.

Спробуйте накреслити графік залежності гіпотетичної STL з ідентифікацією OOP. Скільки класів мали б знати один про одного? Залежностей було б дуже багато . Чи зможете ви включити лише vectorзаголовок, не потрапляючи iteratorі навіть не iostreamзатягуючись? STL робить це легко. Вектор знає про тип ітератора, який він визначає, і це все. Алгоритми STL нічого не знають . Їм навіть не потрібно включати заголовок ітератора, хоча всі вони приймають ітератори як параметри. Що тоді більш модульне?

STL може не дотримуватися правил OOP, як визначає Java, але чи не він досягає цілей OOP? Чи не досягається це багаторазове використання, низька зв'язність, модульність та інкапсуляція?

І чи не можна досягти цих цілей краще, ніж версія, що підтверджена OOP?

Щодо того, чому STL був прийнятий на мову, то сталося кілька речей, які призвели до STL.

Спочатку до C ++ були додані шаблони. Вони були додані майже з тієї ж причини, що і дженерики були додані до .NET. Здавалося б, вдалося писати такі речі, як "контейнери типу T", не викидаючи безпеку типу. Звичайно, реалізація, на яку вони досяглись, була набагато складнішою та потужнішою.

Тоді люди виявили, що доданий до них механізм шаблонів був навіть більш потужним, ніж очікувалося. І хтось почав експериментувати з використанням шаблонів, щоб написати більш загальну бібліотеку. Один натхненний функціональним програмуванням і той, який використовував усі нові можливості C ++.

Він представив це мовному комітету C ++, якому знадобилося досить багато часу, щоб звикнути до нього, тому що це виглядало настільки дивним і різним, але в кінцевому підсумку зрозумів, що він працює краще, ніж традиційні еквіваленти OOP, які вони повинні були б включити інакше . Тож вони внесли кілька коригувань до нього та прийняли його до стандартної бібліотеки.

Це був не ідеологічний вибір, не політичний вибір "хочемо ми бути ОПР чи ні", а дуже прагматичний. Вони оцінили бібліотеку і побачили, що вона працює дуже добре.

У будь-якому випадку, обидві причини, які ви згадуєте за те, що надаєте перевагу STL, абсолютно необхідні.

Стандартна бібліотека C ++ має , щоб бути ефективним. Якщо він менш ефективний, ніж, скажімо, еквівалентний ручний прокатний код C, то люди не користуються ним. Це знизить продуктивність, збільшить ймовірність появи помилок, і загалом просто буде поганою ідеєю.

І STL має для роботи з примітивними типами, так як примітивні типи, все , що є в C, і вони велика частина обох мов. Якби STL не працював із власними масивами, це було б марно .

У вашому запитанні є чітке припущення, що ООП "найкращий". Мені цікаво почути, чому. Ви запитуєте, чому вони "відмовились від класичного ООП". Мені цікаво, чому вони повинні були з цим застрягти. Які переваги він би мав?


22
Це гарний опис, але я хотів би виділити одну деталь. STL не є "продуктом" C ++. Насправді, STL, як концепція, існувала до C ++, а C ++ виявилася ефективною мовою, що має (майже) достатню потужність для загального програмування, тому STL був написаний на C ++.
Ігор Кривоконь

17
Оскільки коментарі постійно доводять це, так, я знаю, що назва STL неоднозначна. Але я не можу придумати кращу назву для "частини стандартної бібліотеки C ++, яка моделюється на STL". Фактична назва для цієї частини стандартної бібліотеки - просто "STL", хоча вона є суто неточною. :) Поки люди не використовують STL як назву для всієї стандартної бібліотеки (включаючи IOStreams та заголовки C stdlib), я задоволений. :)
jalf

5
@einpoklum А що саме ви отримаєте від абстрактного базового класу? Візьмемо std::setяк приклад. Він не успадковує абстрактний базовий клас. Як це обмежує ваше використання std::set? Чи є щось, що ви не можете зробити з a, std::setоскільки воно не успадковується від абстрактного базового класу?
fredoverflow

22
@einpoklum, будь ласка, погляньте на мову Smalltalk, яку Алан Кей створив як мову OOP, коли він винайшов термін OOP. У нього не було інтерфейсів. OOP не стосується інтерфейсів або абстрактних базових класів. Чи збираєтесь ви сказати, що "Java, що не схоже на те, що мав на увазі винахідник терміна OOP, більше OOP, ніж C ++, що також є не що інше, як на увазі винахідник терміна OOP"? Що ти маєш на увазі сказати: "C ++ - недостатньо явавий на мій смак". Це справедливо, але це не має нічого спільного з ООП.
jalf

8
@MasonWheeler, якби ця відповідь була купою кричущої дурниці, ви б не побачили буквально сотні розробників у всьому світі, які голосували +1 за це, лише троє людей роблять інакше
panda-34,

88

Найбільш пряма відповідь на те, що, на мою думку, ви запитуєте / скаржитеся, це таке: Припущення, що C ++ є мовою ООП, є помилковим припущенням.

C ++ - це багатопарадигмальна мова. Вона може бути запрограмована за допомогою принципів OOP, її можна запрограмувати процедурно, її можна запрограмувати загально (шаблони), а за допомогою C ++ 11 (раніше відомого як C ++ 0x) деякі речі можна навіть програмувати функціонально.

Дизайнери C ++ сприймають це як перевагу, тому вони стверджують, що обмеження C ++ діяти як чисто OOP-мова, коли загальне програмування вирішує проблему краще, і, що більш загально , буде кроком назад.


4
"і з C ++ 0x деякі речі можна навіть запрограмувати функціонально" - це можна запрограмувати функціонально без цих функцій, просто більш багатослівно.
Йонас Келькер,

3
@Tyler Дійсно, якщо ви обмежили C ++ чистим OOP, вам залишиться об’єктивна-C.
Justicle

@TylerMcHenry: Щойно запитавши це , я виявив, що я просто вимовив ту ж відповідь, що і ти! Всього один момент. Я хочу, щоб ви додали той факт, що Стандартну бібліотеку не можна використовувати для запису об'єктно-орієнтованого коду.
einpoklum

74

Я розумію, що Stroustrup спочатку віддав перевагу дизайну контейнерів у стилі OOP, а насправді не бачив іншого способу зробити це. Олександр Степанов є відповідальним за STL, і його цілі не включали "зробити його об'єктно орієнтованим" :

Це основний момент: алгоритми визначаються на алгебраїчних структурах. Ще кілька років мені знадобилося зрозуміти, що вам потрібно розширити поняття структури, додавши вимоги до складності до звичайних аксіом. ... Я вважаю, що теорії ітераторів є настільки ж важливими для інформатики, як теорії кілець або банахових просторів є основними для математики. Кожного разу, коли я дивився б на алгоритм, я намагався би знайти структуру, за якою він визначений. Тож, що я хотів зробити, це описати алгоритми загально. Це те, що я люблю робити. Я можу провести місяць, працюючи над добре відомим алгоритмом, намагаючись знайти його загальне уявлення. ...

STL, принаймні для мене, представляє єдиний спосіб можливого програмування. Він дійсно сильно відрізняється від програмування на C ++ тим, що він був представлений і все ще представлений у більшості підручників. Але, бачите, я не намагався програмувати на C ++, я намагався знайти правильний спосіб роботи з програмним забезпеченням. ...

У мене було багато помилкових стартів. Наприклад, я витратив роки, намагаючись знайти певну користь для успадкування та віртуалів, перш ніж зрозумів, чому цей механізм є принципово хибним і його не слід використовувати. Я дуже радий, що ніхто не міг побачити всіх проміжних кроків - більшість з них були дуже нерозумні.

(Він пояснює, чому успадкування та віртуальність - також об'єктно-орієнтована конструкція "була в основному хибною і не повинна використовуватися" в решті інтерв'ю).

Одного разу Степанов подарував свою бібліотеку Stroustrup, Stroustrup та ін. Пройшли геркулесові зусилля, щоб увійти в стандарт ISO C ++ (те саме інтерв'ю):

Підтримка Bjarne Stroustrup була вирішальною. Bjarne дуже хотів STL у стандарті, і якщо Bjarne чогось хоче, він його отримує. ... Він навіть змусив мене внести зміни в STL, які я ніколи б не зробив ні для кого іншого ... він є самою однодумною людиною, яку я знаю. Він робить справи. Йому знадобилося деякий час, щоб зрозуміти, про що йдеться в STL, але коли він це зробив, він був готовий просунути його. Він також вніс свій внесок у STL, відстоюючи думку про те, що більш ніж один спосіб програмування є дійсним - не припиняючи негараздів та ажіотажу більше десятиліття, і переслідуючи поєднання гнучкості, ефективності, перевантаження та безпеки безпеки в шаблони, які зробили можливим STL. Я хотів би досить чітко констатувати, що Bjarne є видатним дизайнером мови мого покоління.


2
Цікаве інтерв'ю. Досить впевнений, що я читав його раніше, ніж певний час тому, але, безумовно, варто пройти ще раз. :)
jalf

3
Одне з найцікавіших інтерв'ю про програмування, яке я коли-небудь читав. Хоча це залишає мене спрагу більш детально ...
Felixyz

Багато скарг, які він висловлює щодо таких мов, як Java ("Ви не можете написати загальний max () на Java, який бере два аргументи певного типу та має зворотне значення того ж типу"), стосувалися лише дуже ранніх версій мови, перш ніж були додані дженерики. Ще з самого початку було відомо, що генеріки з часом будуть додані, хоча (колись з'ясувався життєздатний синтаксис / семантика), тож його закиди в основному безпідставні. Так, дженерики в певній формі потрібні для збереження безпеки типу на мові, яка не є статично введеною, але ні, це не робить OO нікчемним.
Якийсь Гай

1
@SomeGuy Вони самі не скаржаться на Java. Він говорить про " стандартне" OO програмування SmallTalk або, скажімо, Java ". Інтерв'ю - з кінця 90-х (він згадує роботу в SGI, яку він залишив у 2000 році, щоб працювати в AT&T). Generics були додані Java лише у 2004 році у версії 1.5, і вони є відхиленням від "стандартної" моделі OO.
Мельпомена

24

Відповідь знаходимо в цьому інтерв'ю зі Степановим, автором STL:

Так. STL не є об'єктно-орієнтованою. Я думаю, що орієнтована на об’єкти - це майже стільки ж підступ, скільки штучний інтелект. Я ще не бачив цікавого фрагмента коду, який надходить від цих людей з ОО.


Гарний дорогоцінний камінь; Чи знаєте ви, з якого року це?
Кос

2
@Kos, згідно з веб- сайтом web.archive.org/web/20000607205939/http://www.stlport.org/…, перша версія пов’язаної сторінки - з 7 червня 2001 року. Сама сторінка внизу говорить про авторські права 2001- 2008 рік.
alfC

@ Кос Степанов у першій відповіді згадує роботу в SGI. Він покинув SGI у травні 2000 року, тому, імовірно, інтерв'ю є старшим за це.
Мельпомена

18

Чому чистий дизайн OOP для бібліотеки структури даних та алгоритмів був би кращим ?! OOP - це не рішення для кожної речі.

IMHO, STL - це найвишуканіша бібліотека, яку я бачив коли-небудь :)

для вашого питання,

Вам не потрібен поліморфізм часу виконання, STL - це перевага фактично реалізувати Бібліотеку за допомогою статичного поліморфізму, що означає ефективність. Спробуйте написати загальний сортування чи відстань або алгоритм, який застосовується до ВСІХ контейнерів! ваш Сортувати в Java закликає виконувати функції, які динамічні через n-рівнів!

Вам потрібна дурна річ, як Boxing і Unboxing, щоб приховати неприємні припущення так званих Pure OOP мов.

Єдина проблема, яку я бачу зі STL та шаблонами взагалі - це жахливі повідомлення про помилки. Що буде вирішено за допомогою концепції в C ++ 0X.

Порівнювати STL з колекціями на Java - це як порівнювати Тадж-Махал з моїм домом :)


12
Що, Тадж-Махал маленький і вишуканий, а ваш будинок розміром з гори і повний безлад? ;)
jalf

Концепції вже не є частиною c ++ 0x. Деякі повідомлення про помилки можна попередньо видалити, static_assertможливо, використовуючи .
KitsuneYMG

GCC 4.6 покращив повідомлення про помилки в шаблоні, і я вважаю, що 4.7+ навіть краще.
Девід Стоун

Концепція - це, по суті, "інтерфейс", про який просила ОП. Єдина відмінність полягає в тому, що "успадкування" від Concept є неявним (якщо клас має всі правильні функції члена, він автоматично є підтипом Concept), а не явним (клас Java повинен явно заявити, що він реалізує інтерфейс) . Однак як неявна, так і явна підтипізація є дійсним OO, а деякі мови OO мають неявне успадкування, яке працює так само, як і Concepts. Тож, що тут говорять, це в основному "OO sucks: використовуйте шаблони. Але шаблони мають проблеми, тому використовуйте Concepts (які є OO)."
Якийсь Гай

11

шаблонні типи повинні слідувати "концепції" (вхідний ітератор, перехідний ітератор тощо), де фактичні деталі концепції визначаються повністю реалізацією функції / класу шаблону, а не класом типу використовується з шаблоном, який є деяким антивикористанням OOP.

Думаю, ви неправильно розумієте використання шаблонів із застосуванням понять. Наприклад, Forte Iterator - це дуже чітко визначена концепція. Щоб знайти вирази, які повинні бути дійсними для того, щоб клас був ітератором Forward, та їх семантикою, включаючи обчислювальну складність, ви дивитесь на стандарт або на http://www.sgi.com/tech/stl/ForwardIterator.html (ви повинні перейти за посиланнями на вхід, вихід та тривіальний ітератор, щоб побачити все це).

Цей документ є ідеально хорошим інтерфейсом, і "фактичні деталі концепції" визначені саме там. Вони не визначаються реалізацією Forte Iterators, а також вони не визначаються алгоритмами, які використовують Forward Iterators.

Відмінності в обробці інтерфейсів між STL та Java тричі:

1) STL визначає дійсні вирази за допомогою об'єкта, тоді як Java визначає методи, які повинні бути викликані на об'єкті. Звичайно, допустимим виразом може бути виклик методу (член-функція), але цього не повинно бути.

2) Інтерфейси Java - це об'єкти виконання, тоді як STL концепції не видно навіть під час виконання RTTI.

3) Якщо вам не вдасться зробити дійсними необхідні дійсні вирази для концепції STL, ви отримаєте невизначену помилку компіляції, коли інстанціюєте деякий шаблон із типом. Якщо вам не вдалося реалізувати потрібний метод інтерфейсу Java, ви отримаєте певну помилку компіляції, яка сказала це так.

Ця третя частина - якщо вам подобається якийсь час (компіляція) "набору качок": інтерфейси можуть бути неявними. У Java інтерфейси дещо явні: клас "є" Ітерабельний, якщо і лише тоді, коли він каже, що реалізує Iterable. Компілятор може перевірити, чи всі підписи його методів є присутніми та правильними, але семантика все ще неявна (тобто вони задокументовані чи ні, але лише більше коду (одиничні тести) може сказати вам, чи правильна реалізація).

У C ++, як і в Python, і семантика, і синтаксис неявні, хоча в C ++ (і в Python, якщо ви отримуєте сильний набір препроцесора), ви отримуєте певну допомогу від компілятора. Якщо програміст вимагає явного декларування інтерфейсів, схожих на Java, класом реалізації, то стандартним підходом є використання ознак типу (і багатократне успадковування може запобігти занадто багатослівне). Чого бракує, порівняно з Java, - це єдиний шаблон, який я можу інстанціювати зі своїм типом, і який буде компілюватися, якщо і лише якщо всі необхідні вирази дійсні для мого типу. Це дозволить мені сказати, чи я реалізував усі необхідні біти, "перш ніж це використовувати". Це зручність, але це не суть OOP (і вона все ще не перевіряє семантику,

STL може бути або не бути достатньо OO на ваш смак, але він, безумовно, відокремлює інтерфейс чисто від реалізації. Йому не вистачає можливості Java робити відображення через інтерфейси, і він повідомляє про порушення вимог інтерфейсу по-різному.

Ви можете сказати, що функція ... очікує, що Фортератор ітератора лише переглянувши його визначення, де вам потрібно буде переглянути або реалізацію або документацію для ...

Особисто я вважаю, що неявні типи є силою, якщо їх використовувати належним чином. Алгоритм говорить про те, що він робить зі своїми параметрами шаблону, а реалізатор гарантує, що ці речі працюють: це саме загальний знаменник того, що повинні робити "інтерфейси". Крім того, зі STL ви навряд чи будете використовувати, скажімо, std::copyна основі знаходження своєї прямої заяви у файлі заголовка. Програмістам слід розробити те, що функція приймає на основі її документації, а не лише на підписі функції. Це вірно в C ++, Python або Java. Існують обмеження щодо того, чого можна досягти при введенні будь-якою мовою, і намагання використовувати введення тексту, щоб зробити те, чого він не робить (перевірити семантику), було б помилкою.

Однак, алгоритми STL зазвичай називають параметри шаблону таким чином, що дає зрозуміти, яка концепція потрібна. Однак це полягає в наданні корисної додаткової інформації в першому рядку документації, а не в тому, щоб робити декларації більш інформативними. Ви повинні знати більше речей, ніж їх можна інкапсулювати у типи параметрів, тому вам доведеться читати документи. (Наприклад, в алгоритмах, які приймають діапазон введення та ітераторі виводу, ймовірність того, що ітератору виходу потрібно достатньо «місця» для певної кількості виходів, виходячи з розміру діапазону введення та, можливо, значень в ньому. Спробуйте сильно ввести це. )

Ось Bjarne на явно оголошених інтерфейсах: http://www.artima.com/cppsource/cpp0xP.html

У дженериках аргумент повинен бути класу, похідного від інтерфейсу (еквівалент C ++ інтерфейсу - абстрактний клас), визначеного у визначенні загального. Це означає, що всі типи загальних аргументів повинні вписуватися в ієрархію. Це обмежує непотрібні обмеження дизайну і вимагає необґрунтованого передбачення з боку розробників. Наприклад, якщо ви пишете загальний, а я визначаю клас, люди не можуть використовувати мій клас як аргумент до вашого загального, якщо я не знав про вказаний вами інтерфейс і отримав з нього мій клас. Це жорстко.

Дивлячись на це навпаки, при наборі качок ви можете реалізувати інтерфейс, не знаючи, що інтерфейс існує. Або хтось може навмисно написати інтерфейс, щоб ваш клас реалізував його, проконсультувавшись із вашими документами, щоб побачити, що вони не просять нічого, чого ви ще не робите. Це гнучко.


На явно оголошених інтерфейсах два слова: класи типу. (Що вже означає Степанов під поняттям.)
pyon

"Якщо вам не вдасться зробити дійсними необхідні дійсні вирази для концепції STL, ви отримаєте невизначену помилку компіляції, коли інстанціюєте деякий шаблон із типом." - це помилково. Передача чогось у stdбібліотеку, що не відповідає поняттю, як правило, "неправильно сформована, діагностика не потрібна".
Якк - Адам Невраумон

Правда, я грав швидко і вільно з терміном "дійсний". Я мав на увазі, якщо компілятор не може скласти один із необхідних виразів, то він щось повідомить.
Стів Джессоп

8

"OOP для мене означає лише обмін повідомленнями, локальне збереження та захист та приховування державного процесу, а також надзвичайне запізнення всіх речей. Це можна зробити в Smalltalk та LISP. Можливо, є й інші системи, в яких це можливо, але Я їх не знаю ". - Алан Кей, творець Smalltalk.

C ++, Java та більшість інших мов - це дуже далеко від класичного OOP. Однак, сперечатися за ідеології не дуже страшно. C ++ не є чистим у будь-якому сенсі, тому він реалізує функціонал, який, здається, має прагматичний сенс у той час.


7

STL розпочався з наміру забезпечити велику бібліотеку, яка охоплює найчастіше використовуваний алгоритм - з ціллю послідовної поведінки та продуктивності . Шаблон став ключовим фактором, щоб зробити та реалізацію цілі реальною.

Просто для надання ще однієї довідки:

Аль Стівенс опитував Олексія Степанова в березні 1995 року про DDJ:

Степанов пояснив свій досвід роботи та вибір, зроблений у напрямку великої бібліотеки алгоритмів, яка з часом перетворилася на STL.

Розкажіть нам про ваш багаторічний інтерес до загального програмування

..... Тоді мені запропонували роботу в лабораторіях Bell, які працювали в групі C ++ у бібліотеках C ++. Вони запитали мене, чи можу я це зробити на C ++. Звичайно, я не знав С ++ і, звичайно, сказав, що можу. Але я не міг цього зробити на C ++, тому що в 1987 році у C ++ не було шаблонів, необхідних для включення цього стилю програмування. Спадщина була єдиним механізмом для отримання родовитості, і вона була недостатньою.

Навіть зараз успадкування C ++ не дуже корисно для загального програмування. Давайте обговоримо, чому. Багато людей намагаються використовувати успадкування для реалізації структур даних та класів контейнерів. Як ми знаємо зараз, успішних спроб було небагато. Спадкування C ++ та пов'язаний з ним стиль програмування різко обмежені. Неможливо реалізувати дизайн, який включає в себе тривіальну річ, як рівність, використовуючи її. Якщо ви починаєте з базового класу X в корені вашої ієрархії і визначаєте оператора віртуальної рівності для цього класу, який бере аргумент типу X, то отримайте клас Y з класу X. Що таке інтерфейс рівності? Вона має рівність, яка порівнює Y з X. Використовуючи тварин як приклад (люди, що люблять тварин, люблять тварин), визначають ссавців та отримують жирафу від ссавця. Потім визначте член-функціонера, де тварина товаришується з твариною і повертає тварину. Тоді ви отримуєте жирафу від тварини, і, звичайно, вона має функцію, де жирафа поєднується з твариною і повертає тварину. Це точно не те, що ти хочеш. Хоча спаровування може бути не дуже важливим для програмістів на C ++, рівність є. Я не знаю жодного алгоритму, де рівність якоїсь форми не використовується.


5

Основна проблема с

void MyFunc(ForwardIterator *I);

це як безпечно отримати тип речі, яку повертає ітератор? З шаблонами це робиться для вас під час компіляції.


1
Ну, або я: 1. Не намагайтеся це отримати, оскільки я пишу загальний код. Або, 2. Отримайте це за допомогою механізму відображення, який C ++ пропонує сьогодні.
einpoklum

2

На мить давайте розглянемо стандартну бібліотеку як в основному базу даних колекцій та алгоритмів.

Якщо ви вивчали історію баз даних, ви, безперечно, знаєте, що ще на початку бази даних були здебільшого "ієрархічними". Ієрархічні бази даних дуже тісно відповідали класичному OOP - конкретно, різновидові однонаспадкові, наприклад, використовувані Smalltalk.

З часом стало очевидним, що ієрархічні бази даних можуть використовуватися для моделювання майже будь-чого, але в деяких випадках модель єдиного успадкування була досить обмежуючою. Якщо у вас були дерев'яні двері, було б зручно дивитись на неї або як на двері, або як на шматок якоїсь сировини (сталі, дерева тощо)

Отже, вони винайшли бази даних мережевих моделей. Бази даних мережевих моделей дуже тісно відповідають багатократному успадкуванню. C ++ повністю підтримує багатократне успадкування, тоді як Java підтримує обмежену форму (ви можете успадковувати лише один клас, але також можете реалізувати стільки інтерфейсів, скільки вам подобається).

Бази даних ієрархічної моделі та мережевих моделей зникли від загального використання (хоча деякі з них залишаються у досить специфічних нішах). Для більшості цілей вони були замінені реляційними базами даних.

Значна частина реляційних баз даних була багатофункціональною. Реляційна модель функціонально є набором мережевої моделі (що, у свою чергу, є набором ієрархічної моделі).

C ++ значною мірою йшов тим самим шляхом. Відповідність між єдиним успадкуванням та ієрархічною моделлю та між множинним успадкуванням та мережевою моделлю досить очевидна. Відповідність між шаблонами C ++ та ієрархічною моделлю може бути менш очевидною, але все одно вона досить близька.

Я не бачив офіційного підтвердження цього, але я вважаю, що можливості шаблонів - це сукупність тих, що забезпечуються багатократним успадкуванням (що, очевидно, є набором одинарної спадщини). Одна складна частина полягає в тому, що шаблони в основному є статично пов'язаними, тобто все прив'язка відбувається під час компіляції, а не час виконання. Таким чином, формальний доказ того, що успадкування забезпечує надмножину можливостей успадкування, може виявитися дещо складним і складним (або навіть неможливим).

У будь-якому випадку, я думаю, що це більшість справжніх причин, коли C ++ не використовує спадкування для своїх контейнерів - немає реальної причини для цього, оскільки успадкування забезпечує лише підмножину можливостей, що надаються шаблонами. Оскільки шаблони в основному є необхідністю у деяких випадках, вони також можуть бути використані майже скрізь.


0

Як ви робите порівняння з ForwardIterator * 's? Тобто, як ви перевіряєте, чи є у вас предмет, який ви шукаєте, чи ви його передали?

Більшу частину часу я б використовував щось подібне:

void MyFunc(ForwardIterator<MyType>& i)

це означає, що я знаю, що я вказує на MyType, і я знаю, як їх порівняти. Хоча це виглядає як шаблон, насправді це не є (немає ключового слова "шаблон").


ви можете просто скористатися операторами типу <,> і = та не знати, що це (хоча це може бути не тим, що ви мали на увазі)
lhahne

Залежно від контексту, це може не мати ніякого сенсу, інакше вони можуть працювати нормально. Важко сказати, не знаючи більше про MyType, що, імовірно, користувач робить, а ми - ні.
Tanktalus

0

На це питання є багато чудових відповідей. Слід також зазначити, що шаблони підтримують відкритий дизайн. При поточному стані об'єктно-орієнтованих мов програмування доводиться використовувати шаблон відвідувачів при вирішенні таких проблем, а справжній OOP повинен підтримувати багаторазове динамічне прив'язування. Див. Відкриті багатопроцесорні методи для C ++, P. Pirkelbauer, et.al.для дуже цікавого читання.

Ще один цікавий момент шаблонів полягає в тому, що їх можна використовувати і для поліморфізму під час виконання. Наприклад

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;
    }

Зауважте, що ця функція також буде працювати, якщо Valueце якийсь вектор ( не std :: vector, який слід викликатиstd::dynamic_array щоб уникнути плутанини)

Якщо funcвона невелика, ця функція отримає багато від вбудовування. Приклад використання

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

У цьому випадку ви повинні знати точну відповідь (2.718 ...), але легко побудувати просту ODE без елементарного рішення (Підказка: використовуйте поліном у y).

Тепер у вас є великий вираз func, і ви використовуєте вирішувач ODE у багатьох місцях, тому ваш виконуваний файл скрізь забруднюється шаблонами шаблонів. Що робити? Перше, що слід помітити, це те, що працює звичайний вказівник функції. Потім ви хочете додати currying, щоб ви написали інтерфейс і явну інстанцію

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);

Але вищенаведена інстанція працює лише для того double, чому б не написати інтерфейс як шаблон:

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

і спеціалізуються на деяких загальних типах значень:

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)

Якби функція була розроблена спочатку навколо інтерфейсу, ви б змушені були успадковувати цей ABC. Тепер у вас є ця опція, а також вказівник функції, лямбда або будь-який інший об’єкт функції. Ключовим тут є те, що ми повинні мати operator()(), і ми повинні мати можливість використовувати деякі арифметичні оператори для його типу повернення. Таким чином, техніка шаблону в цьому випадку зламається, якби у C ++ не було перевантаження оператора.


-1

Концепція відокремлення інтерфейсу від інтерфейсу та можливості заміни реалізацій не є властивою об'єктно-орієнтованому програмуванню. Я вважаю, що це ідея, яка вилупилась у розробці на основі компонентів, як Microsoft COM. (Дивіться мою відповідь на те, що таке розвиток, орієнтований на компоненти?) Підростаючи та вивчаючи C ++, люди були захоплені спадщиною та поліморфізмом. Тільки до 90-х років люди почали говорити "Програма на" інтерфейс ", а не на" реалізацію "" та "Фаворит" на об'єкт "над" успадкуванням класу ". (обидва з яких цитуються з GoF до речі).

Тоді Java прийшла разом із вбудованим сміттєзбірником та interfaceключовим словом, і раптом стало практично фактично розділяти інтерфейс та реалізацію. Перш ніж це дізнатися, ідея стала частиною ОО. C ++, шаблони та STL передували цьому.


Домовились, що інтерфейси - це не лише ОО. Але поліморфізм здібностей у типовій системі є (це було в Симулі в 60-х роках). Модульні інтерфейси існували в Modula-2 та Ada, але вони працювали в системі типів по-різному, я думаю.
andygavin
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.