Почему C ++ не позволяет вам взять адрес конструктора?


14

Есть ли конкретная причина, по которой это нарушило бы концептуальный язык, или конкретная причина, по которой это технически невозможно в некоторых случаях?

Использование будет с новым оператором.

Редактировать: я собираюсь отказаться от надежды на получение моего "нового оператора" и "оператора нового" прямо и быть прямым.

Суть вопроса в том, почему конструкторы особенные ? Имейте в виду, конечно, что языковые спецификации говорят нам, что является законным, но не обязательно моральным. То, что является законным, обычно определяется тем, что логически согласуется с остальной частью языка, что является простым и лаконичным, и что осуществимо для компиляторов. Возможное обоснование для комитета по стандартам взвешивания этих факторов является обдуманным и интересным - отсюда и вопрос.


Это не было бы проблемой получения адреса конструктора, но могло бы передавать тип. Шаблоны могут сделать это.
Эйфорическая

Что если у вас есть шаблон функции, который вы хотите создать объект, используя конструктор, который будет указан в качестве аргумента функции?
Praxeolitic

1
Будут альтернативы для любого примера, который я могу придумать, но все же, почему конструкторы должны быть особенными? Есть множество вещей, которые вы, скорее всего, не будете использовать в большинстве языков программирования, но особые случаи, подобные этому, обычно приходят с оправданием.
Praxeolitic

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

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

Ответы:


10

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

Альтернативой для Страуструпа было бы выбрать синтаксис для C ++, где конструкторы могли бы иметь имя, отличное от имени класса, - но это предотвратило бы некоторые очень изящные аспекты существующего синтаксиса ctor и сделало бы язык более сложным. Для меня это выглядит высокой ценой только для того, чтобы позволить редко нужную функцию, которую можно легко смоделировать путем "аутсорсинга" инициализации объекта из ctor-объекта другой initфункцией (обычная функция-член, для которой указатель на член может быть создано).


2
И все же, зачем мешать memcpy(buffer, (&std::string)(int, char), size)? (Вероятно, крайне некошерный, но в конце концов это C ++.)
Томас Эдинг

3
извините, но то, что вы написали, не имеет смысла. Я не вижу ничего плохого в том, чтобы указатель на член указывал на конструктор. Кроме того, похоже, что вы что-то цитировали, без ссылки на источник.
BЈовић

1
@ThomasEding: что именно вы ожидаете от этого заявления? Копирование кода сборки строки ctor где-нибудь? Как будет определен «размер» (даже если вы попробуете что-то эквивалентное для стандартной функции-члена)?
Док Браун

Я ожидал бы, что он будет делать то же самое, что и адрес указателя свободной функции memcpy(buffer, strlen, size). Предположительно это скопировало бы сборку, но кто знает. Независимо от того, можно ли вызывать код без сбоев, потребуется знание используемого вами компилятора. То же самое касается определения размера. Это будет зависеть от платформы, но в производственном коде используется множество непереносимых конструкций C ++. Я не вижу причин запрещать это.
Томас Эдинг

@ThomasEding: Ожидается, что соответствующий компилятор C ++ даст диагностику при попытке получить доступ к указателю на функцию, как если бы это был указатель на данные. Несоответствующий компилятор C ++ может делать все что угодно, но он также может обеспечить не-c ++ способ доступа к конструктору. Это не причина добавлять функцию в C ++, которая не используется в соответствующем коде.
Барт ван Инген Шенау

5

Конструктор - это функция, которую вы вызываете, когда объект еще не существует, поэтому он не может быть функцией-членом. Это может быть статичным.

На самом деле конструктор вызывается с указателем this после того, как память была выделена, но до того, как она была полностью инициализирована. Как следствие, конструктор имеет ряд привилегированных функций.

Если бы у вас был указатель на конструктор, то это должен быть либо статический указатель, что-то вроде фабричной функции, либо специальный указатель на что-то, что вызывалось бы сразу после выделения памяти. Это не может быть обычная функция-член и все равно работать как конструктор.

Единственная полезная цель, которая приходит на ум, - это специальный вид указателя, который можно передать оператору new, чтобы он мог косвенно указывать, какой конструктор использовать. Я думаю, это может быть удобно, но это потребует значительного нового синтаксиса, и, вероятно, ответ таков: они думали об этом, и это не стоило усилий.

Если вы просто хотите изменить рефакторинг общего кода инициализации, то обычная функция памяти обычно является достаточным ответом, и вы можете получить указатель на один из них.


Это кажется самым правильным ответом. Я вспоминаю статью много (много) лет назад, касающуюся оператора new и внутренней работы «нового оператора». Оператор new () выделяет пространство. Оператор new вызывает конструктор с этим выделенным пространством. Взятие адреса конструктора является «специальным», потому что вызов конструктора требует места. Доступ для вызова конструктора как это с размещением нового.
Билл Дверь

1
Слово «существует» скрывает детали того, что объект может иметь адрес и иметь выделенную память, но не может быть инициализирован. Что касается функции-члена или нет, я думаю, что получение указателя this делает функцию функцией-членом, поскольку она явно связана с экземпляром объекта (даже если он не инициализирован). Тем не менее, ответ поднимает хороший вопрос: конструктор является единственной функцией-членом, которую можно вызывать для неинициализированного объекта.
Praxeolitic

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

И 12.1: «Конструктор не должен быть виртуальным (10.3) или статическим (9.4)». (мой акцент)
Praxeolitic

1
Дело в том, что если вы компилируете символы отладки и ищите трассировку стека, на самом деле есть указатель на конструктор. То, что я никогда не был в состоянии найти синтаксис для получения этого указателя ( &A::Aне работает ни в одном из компиляторов, которые я пробовал.)
alfC

-3

Это потому, что они не возвращают конструктор, и вы не резервируете место для конструктора в памяти. Как и в случае переменной во время объявления. Например: если вы пишете простую переменную X, то компилятор выдаст ошибку, потому что компилятор не поймет значения этого. Но когда ты пишешь Int x; Затем компилятор узнает, что он является переменной типа int, поэтому он зарезервирует некоторое пространство для переменной.

Вывод: - поэтому вывод таков, что из-за исключения типа возврата он не получит адрес в памяти.


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

2
Вы упустили суть вопроса - в первоначальном посте спрашивалось о том, что нужно взять адрес кода для конструктора, а не результат, предоставленный конструктором. Кроме того, на этой доске, пожалуйста, используйте полные слова: «u» не является приемлемой заменой слова «вы».
BobDalgleish

Г-н praxeolitic, я думаю, что если мы не упомянем какой-либо тип возвращаемого значения, то компилятор не установит конкретную ячейку памяти для ctor, а его местоположение задается внутренне ... компилятор? Если я не прав, поправьте меня, пожалуйста, с правильным ответом
возлюбленный Гоял

А также расскажите мне о ссылочной переменной. Можем ли мы получить адрес ссылочной переменной? Если нет, то какой адрес printf ("% u", & (& (j))); печатает, если & j = x, где x = 10? Поскольку адрес, напечатанный printf, и адрес x не совпадают
любимый Goyal

-4

Я сделаю дикое предположение:

Конструктор и деструктор C ++ вовсе не являются функциями: они являются макросами. Они встраиваются в область, в которой создается объект, и область, в которой объект уничтожается. В свою очередь, нет ни конструктора, ни деструктора, объект просто есть.

На самом деле, я думаю, что другие функции в классе также не являются функциями, а являются встроенными функциями, которые НЕ ДОЛЖНЫ быть встроенными, потому что вы берете их адрес (компилятор понимает, что вы на нем, и не вставляет или не вставляет код в функцию и оптимизирует эту функцию), и, в свою очередь, функция, кажется, «все еще там», хотя этого не произойдет, если вы не укажете ее адрес.

Виртуальная таблица «объекта» 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

Ответ наивный, я просто хотел высказать свое предположение о том, почему нельзя ссылаться на конструктор / деструктор. Я согласен, что в случае виртуальных классов виртуальная таблица должна сохраняться, а адресуемый код должен находиться в памяти, чтобы виртуальная таблица могла ссылаться на нее. Однако классы, которые не реализуют виртуальный класс, кажутся встроенными, как в случае с 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 IUnknown. В свою очередь, компилятор обращается к этим данным напрямую через встроенные или искаженные статические функции.
Дмитрий
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.