Чому C ++ не дозволяє приймати адресу конструктора?


14

Чи є конкретна причина, що це могло б порушити мову концептуально, або конкретна причина, що це технічно неможливо в деяких випадках?

Використання було б у нового оператора.

Редагувати: я збираюся відмовитись від сподівання на те, що я можу "нового оператора" та "нового оператора" отримати прямо та бути прямим.

Суть питання в тому, чому конструктори особливі ? Зверніть увагу, що мовні характеристики говорять нам про те, що є законним, але не обов'язково моральним. Що є законним, як правило, повідомляється тим, що логічно відповідає решті мови, що є простим і стислим, і що можливо для компіляторів реалізувати. Можливе обґрунтування комітету з стандартизації при зважуванні цих факторів є обдуманим та цікавим - звідси і питання.


Це не буде проблемою приймати адресу конструктора, але мати можливість пройти навколо типу. Шаблони можуть це зробити.
Ейфорія

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

1
Будуть альтернативи для будь-якого прикладу, який я можу придумати, але все ж, чому конструктори повинні бути особливими? Є багато речей, які ви, швидше за все, не будете використовувати в більшості мов програмування, але такі випадки, як це зазвичай, виправдовуються.
Праксеоліт

1
@RobertHarvey Питання виникло у мене, коли я збирався набрати заводський клас.
Праксеоліт

1
Цікаво, чи є C ++ 11 std::make_uniqueі чи std::make_sharedможе адекватно вирішити основну практичну мотивацію цього питання. Це шаблонні методи, що означає, що потрібно захопити вхідні аргументи до конструктора, а потім переслати їх фактичному конструктору.
rwong

Відповіді:


10

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

Альтернативою для Stroustrup було б вибрати синтаксис для C ++, де конструктори могли мати назву, відмінну від назви класу, - але це завадило б дуже елегантним аспектам існуючого синтаксису ctor і ускладнило б мову. Для мене це виглядає як висока ціна просто для того, щоб дозволити рідко потрібну функцію, яку можна легко змоделювати за допомогою "аутсорсингу" ініціалізації об'єкта з ctor на іншу initфункцію (нормальна функція-член, для якої може бути вказівник на членів створено).


2
Все-таки навіщо заважати memcpy(buffer, (&std::string)(int, char), size)? (Можливо, надзвичайно не кошерний, але це все-таки C ++.)
Томас Едінг,

3
вибачте, але те, що ви написали, не має сенсу. Я не бачу нічого поганого в тому, щоб вказівник на члена вказував на конструктор. також, це здається, що ви щось цитували, без посилання на джерело.
BЈоviћ

1
@ThomasEding: що саме ти очікуєш зробити з цього твердження? Копіювати код складання рядка ctor деяким часом? Як буде визначатися "розмір" (навіть якщо ви спробуєте щось еквівалентне для стандартної функції члена)?
Док Браун

Я очікував би, що він зробить те саме, що і для вказаної адреси вільного покажчика функції memcpy(buffer, strlen, size). Імовірно, це скопіювало б збори, але хто знає. Незважаючи на те, що код може бути викликаний чи ні, не знадобиться знання про компілятор, який ви використовуєте. Те саме стосується визначення розміру. Це було б дуже залежно від платформи, але у виробничому коді використовується безліч портативних конструкцій C ++. Я не бачу причин забороняти це.
Thomas Eding

@ThomasEding: Очікується, що відповідний компілятор C ++ поставить діагностику при спробі доступу до вказівника функції, як якщо б це був покажчик даних. Невідповідний компілятор C ++ може зробити що завгодно, але вони також можуть забезпечити доступ до конструктора, який не відповідає C ++. Це не є підставою для додавання функції до C ++, яка не використовує відповідний код.
Барт ван Інген Шенау

5

Конструктор - це функція, яку ви викликаєте, коли об'єкт ще не існує, тому він не може бути функцією-членом. Це може бути статичним.

Конструктор насправді викликає цей вказівник після виділення пам'яті, але до її повної ініціалізації. Як наслідок, конструктор має ряд привілейованих функцій.

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

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

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


Це здається найбільш правильною відповіддю. Я пригадую статтю багато (багато) років тому, що стосується нового оператора та внутрішньої роботи «нового оператора». Оператор new () виділяє простір. Новий оператор викликає конструктор з таким виділеним простором. Визначення адреси конструктора є "особливим", оскільки для виклику конструктора потрібен простір. Доступ для виклику конструктора на зразок цього є з розміщенням нового.
Білл Двері

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

Не маючи на увазі, мабуть, вони мають позначення "особливих функцій членів". Пункт 12 стандарту C ++ 11: "Конструктор за замовчуванням (12.1), конструктор копій та оператор призначення копії (12.8), конструктор переміщення та оператор присвоєння переміщення (12.8) та деструктор (12.4) - це функції спеціальних членів ."
Праксеоліт

І 12.1: "Конструктор не повинен бути віртуальним (10.3) або статичним (9.4)." (мій наголос)
Праксеоліт

1
Справа в тому, що якщо ви компілюєте символи налагодження і шукаєте слід стека, насправді є вказівник на конструктор. Що мені ніколи не вдалося - це знайти синтаксис, щоб отримати цей покажчик ( &A::Aне працює в жодному з компіляторів, які я спробував.)
alfC

-3

Це тому, що їх конструктор не є зворотним типом, і ви не залишаєте місця для конструктора в пам'яті. Як і у випадку змінної під час декларування. Наприклад: якщо ви пишете просту змінну X, тоді компілятор генерує помилку, оскільки компілятор не зрозуміє значення цього. Але коли ви пишете Int x; Тоді компілятор дізнається, що це int тип змінної даних, тому він зарезервував деякий простір для змінної.

Висновок: - Отже, висновок полягає в тому, що через виключення типу повернення він не отримає адресу в пам'яті.


1
Код у конструкторі повинен мати адресу в пам'яті, оскільки він повинен бути десь. Не потрібно резервувати простір для цього в стеці, але він повинен бути десь у пам'яті. Ви можете взяти адресу функцій, які не повертають значення. (void)(*fptr)()оголошує вказівник на функцію без значення повернення.
Праксеоліт

2
Ви пропустили суть питання - оригінальний пост запитував про взяття адреси коду для конструктора, а не результат, який надав конструктор. Крім того, на цій дошці будь ласка вживайте повні слова: "u" не є прийнятною заміною для "you".
BobDalgleish

Пане praxeolitic, я думаю, якщо ми не згадаємо про будь-який тип повернення, тоді компілятор не встановить конкретне місце пам'яті для ctor, а його розташування встановлено внутрішньо .... Чи можемо ми отримати адресу будь-якої речі в c ++, яку не задано компілятор? Якщо я помиляюся, то, будь ласка, виправте мене правильною відповіддю
люблячий Гоял

А також розкажіть про контрольну змінну. Чи можемо ми отримати адресу еталонної змінної? Якщо ні, то яка адреса printf ("% u", & (& (j))); друкує, якщо & j = x, де x = 10? Оскільки адреса, надрукована printf та адреса x, не однакова
люблячий Goyal

-4

Я зроблю дику здогадку:

C ++ конструктор і деструктор - це зовсім не функції: вони макроси. Вони вкладаються в область, де створюється об'єкт, і в область, де об'єкт знищується. У свою чергу, немає ні конструктора, ні деструктора, об’єкт просто Є.

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

Віртуальна таблиця «об’єкта» C ++ не схожа на об’єкт JavaScript, де ви можете отримати його конструктор і створити з нього об’єкти під час виконання через new XMLHttpRequest.constructor, а скоріше набір покажчиків на анонімні функції, які виступають засобом для взаємодії з цим об’єктом , виключаючи можливість створення об'єкта. І навіть не має сенсу "видаляти" об'єкт, тому що це як спроба видалити структуру, ви не можете: це просто мітка стека, просто напишіть їй, як вам подобається під іншою міткою: ви вільні використовувати клас як 4 цілих числа:

/* i imagine this string gets compiled into a struct, one of which's members happens to be a const char * which is initialized to exactly your string: no function calls are made during construction. */
std::string a = "hello, world";
int *myInt = (int *)(*((void **)&a));
myInt[0] = 3;
myInt[1] = 9;
myInt[2] = 20;
myInt[3] = 300;

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

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

Короткий зміст: об'єкти C ++ поняття не мають, що вони є; всі інструменти для взаємодії з ними розміщені статично і втрачаються під час виконання. Це робить роботу з класами настільки ж ефективною, як заповнення структур даними та безпосередньо робота з цими даними, без виклику жодних функцій (ці функції впорядковані).

Це абсолютно відрізняється від підходів COM / ObjectiveC, а також javascript, який динамічно зберігає інформацію про тип, за рахунок витрат на накладні витрати, управління пам'яттю, виклики конструкцій, оскільки компілятор не може викинути цю інформацію: це необхідно для динамічної відправки. Це, в свою чергу, дає нам можливість "говорити" з нашою програмою під час виконання програми та розвивати її під час її роботи, маючи відбивні компоненти.


2
вибачте, але деякі частини цієї "відповіді" або помилкові, або небезпечно вводять в оману. На жаль, простір для коментарів є занадто малим, щоб перерахувати їх усі (більшість методів не буде накреслено, це запобіжить віртуальній розсилці та роздуває двійкове середовище; навіть якщо вбудовано, може бути десь доступна адресна копія; нерелевантний приклад коду, що в найгірший випадок псує ваш стек, а в кращому випадку не відповідає вашим припущенням; ...)
hoffmale

Відповідь наївна, я просто хотів висловити свою здогадку про те, чому конструктор / деструктор не може бути посиланням. Я погоджуюся, що у випадку віртуальних класів vtable повинен зберігатися, а адресний код повинен бути в пам'яті, щоб vtable міг посилатися на нього. Однак класи, які не реалізують віртуальний клас, здаються окресленими, як у випадку std :: string. Не все стає впорядкованим, але речі, які, здається, не мінімально поміщаються в "анонімний" блок коду десь у пам'яті. Крім того, як код пошкоджує стек? Звичайно, ми втратили рядок, але в іншому випадку все, що ми зробили, - це переосмислення.
Дмитро

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

залежно від рядкової реалізації, яку ви можете писати над байтами, які ви не хочете. Якщо рядок є чимось на зразок struct { int size; const char * data; };(як, здається, ви припускаєте), ви пишете 4 * 4 байти = 16 байт на адресу пам'яті, де ви лише зарезервували 8 байт на машині x86, тому 8 байт записуються над іншими даними (що може пошкодити ваш стек ). На щастя, std::stringзазвичай існує деяка місцева оптимізація для коротких рядків, тому вона повинна бути достатньо великою для вашого прикладу при використанні деяких основних std-реалізацій.
hoffmale

@hoffmale Ви абсолютно праві, це може бути 4 байти, це може бути 8, а то і 1 байт. Однак, коли ви знаєте розмір рядка, ви також знаєте, що ця пам'ять знаходиться в стеці в поточному обсязі, і ви можете використовувати її як завгодно. Моя думка полягала в тому, що якщо ви знаєте структуру, вона упакована таким чином, що не залежить від будь-якої інформації про клас, на відміну від об'єктів COM, які мають uuid, що ідентифікує їх клас як частину Vtable VUnknown. У свою чергу, компілятор отримує доступ до цих даних безпосередньо за допомогою вбудованих або перероблених статичних функцій.
Дмитро
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.