Чому "void *" неявно не введено в C ++?


30

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

int *a = malloc(sizeof(int));

працює в C, але не в C ++. (Примітка. Я знаю, що ви не повинні використовувати mallocв C ++, або в цьому питанні new, і натомість слід віддавати перевагу розумним покажчикам та / або STL; це запитується з цікавості) Чому стандарт C ++ не дозволяє це неявне відтворення, в той час як стандарт C робить?


3
long *a = malloc(sizeof(int));На жаль, хтось забув змінити лише один тип!
Доваль

4
@Doval: Це все ще легко виправити, використовуючи sizeof(*a)натомість.
wolfPack88

3
Я вважаю, що точка @ ratchetfreak полягає в тому, що причина C робить це неявне перетворення, тому що mallocне може повернути вказівник на виділений тип. new"C ++" повертає вказівник на виділений тип, тому правильно написаний код C ++ ніколи не матиме жодного void *s.
Gort the Robot

3
Що стосується іншого, це не будь-який інший тип вказівника. Потрібно застосовувати лише покажчики даних.
Дедуплікатор

3
@ wolfPack88 у вас історія неправильна. C ++ мав void, C - ні . Коли це ключове слово / ідею було додано до C, вони змінили його відповідно до потреб C. Це було незабаром після того, як типи вказівників взагалі почали перевіряти . Перегляньте, чи можна знайти брошуру з описом K&R C в Інтернеті чи старовинну копію тексту програмування на C, наприклад C Primer букваря Waite Group . У ANSI C було багато функцій, що підтримуються або надихаються C ++, а K&R C - набагато простіше. Тож правильніше, що C ++ розширив C, як він існував у той час, а C, який ви знаєте, був позбавлений C ++.
JDługosz

Відповіді:


39

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

З, як правило, допускається неявна конверсія, навіть якщо більшість шансів на те, що конверсія є помилкою. Це тому, що C припускає, що програміст точно знає, що вони роблять, а якщо ні, то це проблема програміста, а не проблема компілятора.

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

Ви можете запитати, як це дружньо, коли воно насправді вимагає від вас більше вводити текст.

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

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

(*) В ідеалі він буде написаний лише один раз, але він буде читатися щоразу, коли комусь потрібно переглянути його, щоб визначити його придатність для повторного використання, і кожного разу, коли відбувається усунення несправностей, і кожен раз, коли комусь потрібно буде додавати код поруч, а потім кожен раз, коли виникають проблеми з неподалік коду поблизу тощо. Це справедливо у всіх випадках, за винятком сценаріїв "пишіть один раз, запустіть, потім викиньте", і тому не дивно, що більшість мов скриптів мають синтаксис, який полегшує простоту запису з повним зневагою до простоти читання. Коли-небудь думали, що перл абсолютно незрозумілий? Ти не самотній. Подумайте про такі мови як "лише для запису".


C по суті на один крок вище машинного коду. Я можу пробачити С за такі речі.
Qix

8
Програми (незалежно від мови) призначені для читання. Явні операції виділяються.
Матьє М.

10
Варто зауважити, що трансляція void*є більш небезпечною для C ++, оскільки із способом реалізації певних функцій OOP у C ++ вказівник на один і той же об'єкт може мати різне значення залежно від типу вказівника.
Гайда

@MatthieuM. дуже правильно. Дякуємо, що додали, що варто бути частиною відповіді.
Майк Накіс

1
@MatthieuM.: Ах, але ви дійсно не хочете робити все явно. Читання не покращується, якщо читати більше. Хоча в цьому пункті баланс явно є явним.
Дедуплікатор

28

Ось що говорить Stroustrup :

У C ви можете неявно перетворити порожнечу * в T *. Це небезпечно

Потім він показує приклад того, як пустота * може бути небезпечною і каже:

... Отже, в C ++, щоб отримати T * від порожнечі *, вам потрібно чіткий склад. ...

Нарешті, він зазначає:

Одне з найпоширеніших застосувань цього небезпечного перетворення в C - це присвоєння результату malloc () відповідному вказівнику. Наприклад:

int * p = malloc (sizeof (int));

В C ++ використовуйте новий оператор typesafe:

int * p = новий int;

Він детальніше описує це питання в «Дизайні та еволюції C ++» .

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


1
Посилаючись на mallocприклад, варто зазначити, що malloc(очевидно) не буде викликати конструктор, тому, якщо це тип класу, для якого ви виділяєте пам'ять, можливість неявного переходу на тип класу буде вводити в оману.
chbaker0

Це дуже гарна відповідь, мабуть, краще, ніж моя. Мені лише трохи не подобається підхід "аргумент від авторитету".
Майк Накіс

"new int" - ух, як новачок на C ++ у мене виникли проблеми з продумуванням способу додати базовий тип до купи, і я навіть не знав, що ти можеш це зробити. -1 використання для malloc.
Katana314

3
@MikeNakisFWIW, моя мета була доповнити вашу відповідь. У цьому випадку питання було "чому вони це зробили", тому я вважав, що слухання головного дизайнера є виправданим.
Gort the Robot

11

У C немає необхідності призначати порожнечу * до будь-якого іншого типу вказівника, це завжди безпечно просувається.

Це завжди рекламується, так, але навряд чи безпечно .

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


Розглянемо загалом ці 3 підходи до перетворення типів:

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

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


Доїхати до 3 було дуже важко, за винятком додавались пізніше, а шаблони - ще пізніше.
JDługosz

Ну, шаблони НЕ дійсно необхідні для такої повної системи безпечного типу: Хиндли-Мілнера на основі мов без них обійтися досить добре, без неявних перетворень на всіх і немає необхідності виписувати типів Явно або. (Звичайно, ці мови покладаються на стирання / вивезення сміття / поліморфізм вищих видів, чого C ++ скоріше уникає.)
ліворуч близько

1

За визначенням, недійсний вказівник може вказувати на що завгодно. Будь-який вказівник може бути перетворений на недійсний вказівник, і, таким чином, ви зможете конвертувати назад, отримуючи точно таке ж значення. Однак вказівники на інші типи можуть мати такі обмеження, як обмеження вирівнювання. Наприклад, уявіть собі архітектуру, де символи можуть займати будь-яку адресу пам’яті, але цілі числа повинні починатися з рівних меж адреси. У певних архітектурах цілі вказівники можуть навіть нараховувати одночасно 16, 32 або 64 біти, так що char * може насправді мати кратне числове значення int *, вказуючи на одне місце в пам'яті. У цьому випадку перетворення з пустоти * фактично закругляє біти, які неможливо відновити і тому не є оборотними.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.