Чи погана практика використовувати компілятор C ++ лише для перевантаження функцій?
Позиція ІМХО, так, і мені потрібно стати шизофреніком, щоб відповісти на цю, оскільки я люблю обидві мови, але це не має нічого спільного з ефективністю, а більше схоже на безпеку та ідіоматичне використання мов.
C сторона
З точки зору С, я вважаю, що це марно, щоб ваш код вимагав C ++ просто для використання перевантаження функцій. Якщо ви не використовуєте його для статичного поліморфізму з шаблонами C ++, це такий тривіальний синтаксичний цукор, отриманий в обмін на перехід на зовсім іншу мову. Крім того, якщо ви хочете експортувати свої функції в диліб (це може бути, а може, і не бути практичним питанням), ви вже не можете робити це дуже практично для широкого споживання з усіма символами, керованими іменами.
C ++ сторона
З точки зору C ++, ви не повинні використовувати C ++, як C, з функцією перевантаження. Це не стилістичний догматизм, а той, що стосується практичного використання повсякденного C ++.
Ваш звичайний вид C-коду є достатньо розумним і "безпечним" для запису, якщо ви працюєте проти системи типу C, яка забороняє такі речі, як копіювальні диски structs
. Коли ви працюєте в набагато багатшій системі типу C ++, щоденні функції, які мають величезне значення, як memset
і memcpy
не стають функціями, на які слід постійно спиратися. Натомість - це функції, яких ви, як правило, хочете уникати, як чума, оскільки з типами C ++ ви не повинні поводитися з ними як із сирими бітами та байтами, які потрібно копіювати та переміщувати навколо та звільняти. Навіть якщо ваш код використовує лише такі речі, як memset
примітиви та POD UDT на даний момент, той момент, коли хтось додасть ctor до будь-якого UDT, який ви використовуєте (включаючи просто додавання члена, який вимагає такого, наприкладstd::unique_ptr
член) проти таких функцій або віртуальної функції або чогось подібного, це робить все ваше звичайне кодування у стилі C сприйнятливим до не визначеного поведінки. Візьміть це від самого Герб Саттера:
memcpy
і memcmp
порушують систему типів. Використовувати memcpy
для копіювання об’єктів - це як заробляти гроші за допомогою фотокопіювального пристрою. Використовувати memcmp
для порівняння об’єктів - це як порівняння леопардів, підраховуючи їх плями. Інструменти та методи можуть зробити цю роботу, але вони занадто грубі, щоб зробити це прийнятним. Об'єкти C ++ - це приховування інформації (можливо, це найвигідніший принцип в інженерії програмного забезпечення; див. Пункт 11): Об'єкти приховують дані (див. Пункт 41) і розробляють точні абстракції для копіювання цих даних через конструктори та оператори присвоєння (див. Пункти 52 до 55) . Бульдоз над усім, що memcpy
є, є серйозним порушенням приховування інформації, і часто призводить до витоку пам'яті та ресурсів (у кращому випадку), збоїв (гірше) або невизначеної поведінки (найгірше) - Стандарти кодування C ++.
Так багато розробників C не погоджуються з цим і це правильно, оскільки філософія застосовується лише в тому випадку, якщо ви пишете код на C ++. Ви , швидше за все , будете писати дуже проблемний код , якщо ви використовуєте такі функції , як memcpy
весь час в коді , який будує , як C ++ , але це абсолютно нормально , якщо ви робите це в C . Дві мови в цьому плані сильно відрізняються через відмінності в системі типів. Дуже спокусливо переглянути підмножину функцій, які мають ці дві спільні, і вважають, що одну можна використовувати як і іншу, особливо з боку C ++, але C + код (або C-- код), як правило, набагато проблематичніший, ніж як C, так і C ++ код.
Крім того, ви не повинні використовувати, скажімо, malloc
в контексті стилю С (що не означає, що EH), якщо він може викликати будь-які функції C ++, які можуть кинути, оскільки тоді у вас є неявна точка виходу у вашій функції в результаті виняток, який ви не можете ефективно зловити на написанні коду в стилі C, перш ніж матимете змогу в free
цій пам'яті. Таким чином , всякий раз , коли у вас є файл , який будує в C ++ з .cpp
розширенням або що - то , і він робить все ці типи речей , як malloc
, memcpy
, memset
,qsort
і т.д., то він задає проблеми далі за лінією, якщо ні, якщо це не детальна інформація про клас, який працює лише з примітивними типами, і тоді він все ще повинен робити обробку винятків, щоб бути безпечним для виключень. Якщо ви пишете код на С ++ замість цього ви хочете , щоб в основному покладатися на RAII і використовувати такі речі , як vector
, unique_ptr
, shared_ptr
і т.д., і уникнути всі нормальне кодування C-стилю , коли це можливо.
Причина, коли ви можете грати з лезами на бритві у типах даних C та рентгенівських знімків та грати з їх бітами та байтами, не схильні завдати колективної шкоди команді (хоча ви все одно можете пошкодити себе в будь-якому випадку), не тому, що C типи можуть робити, але через те, що вони ніколи не зможуть зробити. У момент, коли ви розширите систему типу C, щоб включити такі функції C ++, як ctors, dtors та vtables, а також обробку винятків, весь ідіоматичний код C став би набагато небезпечнішим, ніж зараз, і ви побачите новий вид філософія та мислення, що розвиваються, що заохочуватиме зовсім інший стиль кодування, як ви бачите в C ++, який тепер вважає навіть використання неправомірної поведінки вказівника для класу, який керує пам'яттю, на відміну, скажімо, від ресурсу, відповідного RAII unique_ptr
. Такий спосіб мислення не розвивався з абсолютного почуття безпеки. Він розвинувся з того, що спеціально C ++ потрібно захистити від таких функцій, як обробка винятків, враховуючи те, що він дозволяє лише через систему типів.
Виняток-Безпека
Знову ж таки, коли ви перебуваєте на землі C ++, люди очікують, що ваш код буде безпечним для винятків. Люди можуть підтримувати ваш код у майбутньому, враховуючи, що він уже написаний і компілюється в C ++, і просто використовувати std::vector, dynamic_cast, unique_ptr, shared_ptr
і т.д. код. У цей момент ми маємо стикатися з шансом, що все викине, і тоді, коли ти візьмеш ідеально чудовий і чудовий код C таким чином:
int some_func(int n, ...)
{
int* x = calloc(n, sizeof(int));
if (x)
{
f(n, x); // some function which, now being a C++ function, may
// throw today or in the future.
...
free(x);
return success;
}
return fail;
}
... це тепер зламано. Це потрібно переписати, щоб бути безпечним для винятків:
int some_func(int n, ...)
{
int* x = calloc(n, sizeof(int));
if (x)
{
try
{
f(n, x); // some function which, now being a C++ function, may
// throw today or in the future (maybe someone used
// std::vector inside of it).
}
catch (...)
{
free(x);
throw;
}
...
free(x);
return success;
}
return fail;
}
Валовий! Ось чому більшість розробників C ++ вимагають цього:
void some_func(int n, ...)
{
vector<int> x(n);
f(x); // some function which, now being a C++ function, may throw today
// or in the future.
}
Наведене вище відповідає безпечному для винятку коду типу RAII такого типу, який розробники C ++, як правило, схвалюють, оскільки функція не просочить жоден рядок коду, який викликає неявний вихід у результаті throw
.
Виберіть мову
Вам слід або використовувати систему типів та філософію C ++ з RAII, виняток безпеки, шаблони, OOP тощо, або охоплювати C, яка значною мірою обертається навколо необроблених бітів і байтів. Ви не повинні укладати недобросовісний шлюб між цими двома мовами, а натомість розділяти їх на різні мови, щоб поводитись дуже по-різному, а не розмивати їх разом.
Ці мови хочуть вийти за вас заміж. Ви, як правило, повинні вибрати один замість того, щоб побачитись та обдурити обох. Або ви можете бути полігамістом, як я, і виходити заміж за обох, але вам доведеться повністю переключити своє мислення, проводячи час один над одним і тримати їх добре відокремленими один від одного, щоб вони не воювали один з одним.
Бінарний розмір
Щойно з цікавості я спробував взяти свою безкоштовну реалізацію списку та орієнтир зараз і перенести його на C ++, оскільки мені стало цікаво про це:
[...] не знаю, як це виглядатиме для C, оскільки я не використовував компілятор C.
... і хотів дізнатись, чи би бінарний розмір взагалі надувався лише таким чином, як C ++. Це вимагало від мене розсипати явні ролики по всьому місцю, яке було нечітко (одна з причин мені подобається насправді краще писати речі низького рівня, такі як розподільники та структури даних в С), але зайняла лише хвилина.
Це було лише порівнянням 64-розрядної версії MSVC для простого консольного додатка та з кодом, який не використовував жодних функцій C ++, навіть не перевантажуючи оператора - лише різниця між побудовою його на C та використанням, скажімо, <cstdlib>
замість <stdlib.h>
і такі речі, але я був здивований, виявивши, що це змінило нульовий розмір у бінарному розмірі!
Двійковий код був 9,728
байтами, коли він був побудований в C, а також 9,278
байтами, коли компілювався як код C ++. Я насправді цього не очікував. Я думав, що такі речі, як EH, принаймні трохи додадуть туди (думав, що це буде принаймні на сто байт), хоча, напевно, він міг зрозуміти, що не потрібно додавати інструкції, пов'язані з ЕГ, оскільки я просто використовуючи стандартну бібліотеку С і нічого не кидає. Я щось думавтак чи інакше додасть бінарний розмір, як RTTI. У всякому разі, це було якось круто, щоб це побачити. Звичайно, я не думаю, що ви повинні узагальнювати цей результат, але це мене принаймні трохи вразило. Це також не впливало на орієнтири, і, природно, так, оскільки я думаю, що однаковий отриманий бінарний розмір також означав ідентичні інструкції на машині.
Це означає, що хто піклується про двійковий розмір із вищезазначеними питаннями безпеки та техніки? Отже, знову ж таки, виберіть мову і сприйміть її філософію, а не намагайтеся її бастардити; ось що я рекомендую.
//
коментарі. Якщо це працює, чому б і ні?