Что означает создание объекта с помощью фигурных скобок в C ++? - PullRequest
8 голосов
/ 22 апреля 2009

Допустим, у меня есть структура, определенная как:

typedef
struct number{
    int areaCode;
    int prefix;
    int suffix;
} PhoneNumber;

Когда я создаю экземпляр этой структуры, если я использую следующий синтаксис:

PhoneNumber homePhone = {858, 555, 1234};

... какой конструктор он вызывает? Конструктор по умолчанию, или конструктор копирования, или его вообще нет, потому что он не вызывает 'new'?

Настоящая цель этого вопроса - выяснить, как я могу добавить четвертое поле. Поэтому я хочу переопределить мою структуру как:

typedef
struct number{
    int areaCode;
    int prefix;
    int suffix;
    int extension; // NEW FIELD INTRODUCED
} PhoneNumber;

Итак, теперь я могу создавать новые объекты PhoneNumber с четырьмя полями:

PhoneNumber officePhone = {858, 555, 6789, 777}

Однако у меня есть сотни таких экземпляров PhoneNumber, уже созданных только с 3 полями (xxx, xxx, xxxx). Поэтому я не хочу проходить и изменять КАЖДУЮ отдельную реализацию моего объекта PhoneNumber, который уже определен. Я хочу быть в состоянии оставить их в покое, но все же иметь возможность создавать новые экземпляры телефонных номеров с четырьмя полями Поэтому я пытаюсь выяснить, как я могу переписать конструктор, чтобы мои существующие трехпараметрические экземпляры не сломались, но он также будет поддерживать мои новые четырехпараметрические экземпляры.

Когда я пытаюсь определить переопределяющий конструктор по умолчанию, который принимает 3 поля и устанавливает четвертое значение по умолчанию '0', я получаю ошибки (в части кода, а не в определении конструктора), жалуясь на то, что мой объект должен инициализироваться конструктором, а не {...}. Так что кажется, что если я переопределю конструктор по умолчанию, я больше не смогу использовать фигурные скобки для создания моих новых объектов?

Извините, если это полностью отклоняется от первоначальных вопросов.

Ответы [ 7 ]

4 голосов
/ 22 апреля 2009

Члены фактически инициализируются копией. Конструктор по умолчанию для каждого из них не вызывается, и operator= не используется, что противоречит тому, что предлагают некоторые другие ответы. Это может быть показано с помощью программного обеспечения под названием geordi - альтернативно, читая стандарт. Я покажу "забавный" способ использования этого программного обеспечения. У него есть класс tracked::B, который может показать нам, когда вызываются конструкторы / операторы копирования или деструкторы / операторы копирования. Вывод, который он показывает (TRACK ограничивает отслеживание оператором, следующим за ним):

B1*(B0) B1~

Я использовал этот код

struct T { tracked::B b; }; int main() { tracked::B b; TRACK T t = { b };  }

Как видите, второй объект B, являющийся членом локальной переменной t, инициализируется копией из другого объекта b. Конечно, оператор присваивания не активирован. Вы можете прочитать об этом в 12.6.1/2 в Стандарте, если хотите.

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

Все другие элементы, которые не были явно инициализированы в агрегате, являются инициализированными значениями. Значение-инициализация представляет собой смесь инициализации по умолчанию и нулевой инициализации. На самом деле, если член имеет тип, который имеет конструктор, объявленный пользователем, то этот конструктор вызывается. Если у него есть тип, который не имеет объявленный пользователем конструктор, то каждый его член инициализируется значением. Для встроенных типов (int, bool, pointers, ...) инициализация значения такая же, как инициализация нуля (это означает, что такая переменная станет нулевой). Следующее будет инициализировать каждый элемент в ноль - кроме первого (а), который будет один:

struct T { int a, b, c; }; int main() { T t = { 1 }; }

Эти правила инициализации действительно страшны, особенно потому, что в версии C ++ 2003 года была введена инициализация этого значения. Он не был частью стандарта с 1998 года. Если вас больше интересуют эти инициализации, заключенные в скобки, вы можете прочитать Как инициализировать вложенные структуры в C ++? .

3 голосов
/ 22 апреля 2009

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

Вместо этого члены остаются неинициализированными; вы инициализируете их с помощью фигурной скобки.

Интересно, что это:

PhoneNumber homePhone = {858, 555, 1234};

Результаты в этой сборке (GCC 4.0.1, -O0):

movl  $858, -20(%ebp)
movl  $555, -16(%ebp)
movl  $1234, -12(%ebp)

Не так много сюрпризов. Сборка является встроенной функцией, содержащей приведенный выше оператор C ++. Значения (начиная с $) перемещаются (movl) в смещения в стек (регистр ebp). Они отрицательны, потому что ячейки памяти для членов структуры предшествуют коду инициализации.

Если вы не полностью инициализируете структуру, то есть пропустите некоторые элементы, например, так:

PhoneNumber homePhone = {858, 555};

... тогда я получаю следующий код сборки:

movl  $0, -20(%ebp)
movl  $0, -16(%ebp)
movl  $0, -12(%ebp)
movl  $858, -20(%ebp)
movl  $555, -16(%ebp)

Похоже, что компилятор затем делает что-то очень похожее на вызов конструктора по умолчанию, за которым следует присваивание. Но опять же, это встроенная функция вызова, а не вызов функции.

Если, с другой стороны, вы определяете конструктор по умолчанию, который инициализирует элементы заданными значениями, например, так:

struct PhoneNumber {
  PhoneNumber()
    : areaCode(858)
    , prefix(555)
    , suffix(1234)
  {
  }

  int areaCode;
  int prefix;
  int suffix;
};

PhoneNumber homePhone;

Затем вы получаете ассемблерный код, который фактически вызывает функцию и инициализирует элементы данных через указатель на структуру:

movl  8(%ebp), %eax
movl  $858, (%eax)
movl  8(%ebp), %eax
movl  $555, 4(%eax)
movl  8(%ebp), %eax
movl  $1234, 8(%eax)

Каждая строка, которая идет movl 8(%ebp), %eax, устанавливает значение указателя (eax-регистр) на начало данных структуры. В других строках eax используется напрямую со смещением 4 и смещением 8, аналогично прямой адресации стека в предыдущих двух примерах.

Конечно, все это относится к реализации компилятора, но я был бы удивлен, если бы другие компиляторы сделали что-то необычное.

2 голосов
/ 22 апреля 2009

Инициализация - это, по крайней мере для меня, одна из самых сложных частей стандарта C ++. Используемый синтаксис: Aggregate x = { 1, 2, 3, 4 }; определяет инициализацию агрегатного типа с первыми четырьмя членами, которым присвоены значения 1, 2, 3 и 4.

В качестве примечания к вашему конкретному случаю (структура выросла с 3 до 4 элементов), поля, отсутствующие в списке инициализации между фигурными скобками, будут значение инициализировано , что, в частности, для скалярных типы эквивалентны нулевой инициализации , что само по себе аналогично присвоению 0. Таким образом, ваш четвертый элемент будет инициализирован в 0.

Ссылки

Все это определено в стандарте C ++, глава 8.5, а точнее в 8.5.1 Aggregates. Агрегаты не будут инициализированы каким-либо неявно объявленным конструктором по умолчанию. Если вы используете приведенный выше синтаксис, вы просите компилятор инициализировать заданные поля указанными значениями. Любое дополнительное поле в совокупности должно быть значение инициализировано

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

Инициализация нуля определяется как установка значения 0 для скалярных типов. Для классов каждый член данных класса и базовый класс будут инициализированными нулями . Для союзов первый член (только) будет инициализирован ноль , для массивов все члены будут инициализирован ноль .

2 голосов
/ 22 апреля 2009

Структура в C ++ похожа на класс. Вызывается конструктор по умолчанию. После этого каждое поле копируется с его оператором присваивания.

1 голос
/ 22 апреля 2009

Это фактически вызов ctor по умолчанию; происходит то, что структура распределяется, и каждому значению присваивается значение по умолчанию "=".

0 голосов
/ 22 апреля 2009

В вашем типе нет задействованного конструктора, поэтому необходимо изменить существующий трехпараметрический синтаксис на сайте инициализации агрегата.

Если только семантика вашего недавно добавленного поля т.е. этот новый тип поля «с нуля учитывает дизайн» (идиома, рекомендованная программированием .NET, Брэдом и Ко и т. д. в бессмысленных рекомендациях по проектированию), вы

не может:

a) предоставить что-то более значимое в качестве параметров по умолчанию для C ++, если с некоторой магией был задействован какой-либо метод (скоро появятся дополнительные / необязательные параметры, которые скоро будут доступны в магазине C # для массового рынка возле бакалейной лавки web 4.0 и для старого COM)

и вы не можете

b) следовать шаблону, подобному C #, при проектировании типов значений с недопустимыми маркерами значений в виде 0 (что в данном случае, вероятно, очень плохо и плохо в целом, если у вас нет контроля над константами - то, что библиотеки уровня источника делают очень ну и все-JavaTM MS-подобные фреймворки отстойные).

Короче говоря, если 0 является допустимым значением, вы набиты и что-то, что большинство авторов компиляторов сочли полезным до появления какого-либо управляемого стиля или идиомы; но это не обязательно правильно.

В любом случае, у вас больше шансов не на C ++ или C #. Это генерация кода, т.е. более либеральное метапрограммирование, чем современный C ++, запускает «шаблонизацию». Вы найдете, что это похоже на аннотацию массива JSON и может использовать либо дух, либо (мой совет будет) бросать свои собственные утилиты. Это помогает в долгосрочной перспективе, и в конце концов вам захочется стать лучше (то есть то, что они называют хромым моделированием, иначе Осло в .NET).

[подобные проблемы распространены во многих языках, это недостаток всех языков в стиле C (C, C ++, Java, C #, вы называете это); очень похож на хакерские массивы массивов, необходимые для любого типа междоменной или семантической работы ввода-вывода и очевидные даже в веб-технологиях, таких как SOAP и т. д. новые переменные биты C ++ 0x здесь тоже мало помогают, но, видимо, могут увеличьте время компиляции, если вы решите поиграть с некоторыми шаблонами Кому интересно :)]

0 голосов
/ 22 апреля 2009

Однако у меня есть сотни таких Экземпляры PhoneNumber уже созданы только с 3 полями (ххх, ххх, хххх). Так что я не хочу проходить и изменить КАЖДЫЙ отдельный экземпляр мой объект PhoneNumber, который уже определены.

Нет проблем, вы просто вызываете update-instance-for-redefined-class и ..... эм, не обращайте внимания. Продолжить, чтобы отметить это "бесполезное"

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...