Рассматривать:
struct Person
{
int height;
int weight;
int age;
};
int main()
{
Person p { .age = 18 };
}
Приведенный выше код допустим в C99, но не разрешен в C ++ 11.
Что это было C ++ 11 Обоснование стандартного комитета для исключения поддержки такой удобной функции?
C ++ имеет конструкторы. Если имеет смысл инициализировать только один элемент, то это можно выразить в программе, реализовав соответствующий конструктор. Это своего рода абстракция, которую продвигает C ++.
С другой стороны, назначенная функция инициализаторов больше связана с раскрытием и упрощением доступа к членам непосредственно в коде клиента. Это приводит к таким вещам, как наличие человека 18 лет (лет?), Но с ростом и весом ноль.
Другими словами, назначенные инициализаторы поддерживают стиль программирования, в котором раскрываются внутренние компоненты, и клиенту предоставляется гибкость в выборе способа использования типа.
C ++ больше заинтересован в том, чтобы придать гибкость стороне дизайнер вместо этого типа, поэтому дизайнеры могут упростить использование типа и затруднить его использование. Контроль дизайнера за инициализацией типа является частью этого: дизайнер определяет конструкторы, инициализаторы в классе и т. Д.
15 июля 17 года P0329R4 был принят в C ++ 20 стандарт: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
Это приносит ограниченную поддержку c99Назначенные инициализаторы. Это ограничение описывается следующим образом в C.1.7 [diff.decl] .4, учитывая:
struct A { int x, y; };
struct B { struct A a; };
Следующие назначенные инициализации, действительные в C, ограничены в C ++:
struct A a = { .y = 1, .x = 2 }
недопустимо в C ++, потому что указатели должны появляться в порядке объявления членов данныхint arr[3] = { [1] = 5 }
недопустим в C ++, потому что инициализация массива не поддерживаетсяstruct B b = {.a.x = 0}
недопустимо в C ++, потому что указатели не могут быть вложеннымиstruct A c = {.x = 1, 2}
недопустим в C ++, потому что либо все, либо ни один из членов данных не должен быть инициализирован указателямиЗа C ++ 17 и раньше Boost на самом деле имеет поддержка назначенных инициализаторов и были многочисленные предложения добавить поддержку C ++ стандарт, например: n4172 а также Предложение Дэрил Уокер добавить обозначение в инициализаторы. Предложения ссылаются на реализацию c99Назначенные инициализаторы в Visual C ++, gcc и Clang утверждают:
Мы считаем, что изменения будут относительно простыми для реализации
Но стандартный комитет неоднократно отклоняет такие предложения, заявив:
EWG обнаружила различные проблемы с предложенным подходом и не думала, что можно попытаться решить проблему, так как она была опробована много раз и каждый раз, когда она терпела неудачу
Комментарии Бена Фойгта помогли мне увидеть непреодолимые проблемы с этим подходом; дано:
struct X {
int c;
char a;
float b;
};
В каком порядке эти функции будут вызываться в c99: struct X foo = {.a = (char)f(), .b = g(), .c = h()}
? Удивительно, но в c99:
Порядок вычисления подвыражений в любом инициализаторе является неопределенным [1]
(Visual C ++, НКУ, и Clang, похоже, ведут себя согласованно, поскольку все они будут делать звонки в этом порядке 🙂
h()
f()
g()
Но неопределенный характер стандарта означает, что если бы эти функции имели какое-либо взаимодействие, результирующее состояние программы также было бы неопределенным, и компилятор не предупредит вас: Есть ли способ получить предупреждение о неправильном поведении назначенных инициализаторов?
C ++ делает иметь строгие требования к списку инициализаторов 11.6.4 [dcl.init.list] 4:
В пределах списка инициализатора списка фигурных скобок предложения инициализатора, включая любые, которые являются результатом расширений пакета (17.5.3), оцениваются в порядке, в котором они появляются. Таким образом, каждое вычисление значения и побочный эффект, связанный с данным предложением инициализатора, секвенируются перед каждым вычислением значения и побочным эффектом, связанным с любым предложением инициализатора, которое следует за ним в списке через запятую списка инициализатора.
Так C ++ Служба поддержки потребовала бы, чтобы это было выполнено в следующем порядке:
f()
g()
h()
Нарушение совместимости с предыдущими c99 Реализации.
Как обсуждалось выше, эта проблема была обойдена ограничениями на Назначенные инициализаторы, принятые в C ++ 20. Они обеспечивают стандартизированное поведение, гарантирующее порядок выполнения назначенных инициализаторов.
Назначенные инициализаторы в настоящее время включены в C ++ 20 основной работы: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf так что мы могли бы наконец увидеть их!
Немного хакерства, так что просто делюсь ради удовольствия.
#define with(T, ...)\
([&]{ T ${}; __VA_ARGS__; return $; }())
И используйте это как:
MyFunction(with(Params,
$.Name = "Foo Bar",
$.Age = 18
));
который расширяется до:
MyFunction(([&] {
Params ${};
$.Name = "Foo Bar", $.Age = 18;
return $;
}()));
Две основные особенности C99 что в C ++ 11 Lacks упоминается «Назначенные инициализаторы и C ++».
Я думаю, что «назначенный инициализатор» связан с потенциальной оптимизацией. Здесь я использую «gcc / g ++» 5.1 в качестве примера.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
int x;
int y;
};
const struct point a_point = {.x = 0, .y = 0};
int foo() {
if(a_point.x == 0){
printf("x == 0");
return 0;
}else{
printf("x == 1");
return 1;
}
}
int main(int argc, char *argv[])
{
return foo();
}
Мы знали во время компиляции, a_point.x
равен нулю, поэтому мы могли ожидать, что foo
оптимизирован в единый printf
,
$ gcc -O3 a.c
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function foo:
0x00000000004004f0 <+0>: sub $0x8,%rsp
0x00000000004004f4 <+4>: mov $0x4005bc,%edi
0x00000000004004f9 <+9>: xor %eax,%eax
0x00000000004004fb <+11>: callq 0x4003a0 <printf@plt>
0x0000000000400500 <+16>: xor %eax,%eax
0x0000000000400502 <+18>: add $0x8,%rsp
0x0000000000400506 <+22>: retq
End of assembler dump.
(gdb) x /s 0x4005bc
0x4005bc: "x == 0"
foo
оптимизирован для печати x == 0
только.
Для версии C ++,
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
point(int _x,int _y):x(_x),y(_y){}
int x;
int y;
};
const struct point a_point(0,0);
int foo() {
if(a_point.x == 0){
printf("x == 0");
return 0;
}else{
printf("x == 1");
return 1;
}
}
int main(int argc, char *argv[])
{
return foo();
}
И это вывод оптимизированного кода сборки.
g++ -O3 a.cc
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function _Z3foov:
0x00000000004005c0 <+0>: push %rbx
0x00000000004005c1 <+1>: mov 0x200489(%rip),%ebx # 0x600a50 <_ZL7a_point>
0x00000000004005c7 <+7>: test %ebx,%ebx
0x00000000004005c9 <+9>: je 0x4005e0 <_Z3foov+32>
0x00000000004005cb <+11>: mov $0x1,%ebx
0x00000000004005d0 <+16>: mov $0x4006a3,%edi
0x00000000004005d5 <+21>: xor %eax,%eax
0x00000000004005d7 <+23>: callq 0x400460 <printf@plt>
0x00000000004005dc <+28>: mov %ebx,%eax
0x00000000004005de <+30>: pop %rbx
0x00000000004005df <+31>: retq
0x00000000004005e0 <+32>: mov $0x40069c,%edi
0x00000000004005e5 <+37>: xor %eax,%eax
0x00000000004005e7 <+39>: callq 0x400460 <printf@plt>
0x00000000004005ec <+44>: mov %ebx,%eax
0x00000000004005ee <+46>: pop %rbx
0x00000000004005ef <+47>: retq
Мы это видим a_point
на самом деле не является постоянной величиной времени компиляции.