C ++ 11이 C99로 지정된 이니셜 라이저 목록을 지원하지 않는 이유는 무엇입니까? [닫은]


121

치다:

struct Person
{
    int height;
    int weight;
    int age;
};

int main()
{
    Person p { .age = 18 };
}

위 코드는 C99에서는 유효하지만 C ++ 11에서는 유효하지 않습니다.

가 무엇이였습니까 이러한 편리한 기능에 대한 지원을 배제한 표준위원회의 근거는 무엇입니까?


10
디자인위원회가 그것을 포함시키는 것은 분명히 말이되지 않았거나 단순히 회의에 나오지 않았습니다. C99 지정된 이니셜 라이저가 있다는 지적이의 가치가 어느하지 는 C ++ 사양 버전. 생성자는 선호되는 초기화 구조로 보이며, 올바른 이유가 있습니다. 올바르게 작성하면 일관성있는 객체 초기화를 보장합니다.
— Robert Harvey

19
당신의 추론은 거꾸로되어 있습니다. 언어 는 기능이 없다는 이유가 필요하지 않습니다 . 기능이 없다는 이유와 그에 강한 이유가 필요합니다. C ++는 그 자체로 충분히 부풀어 있습니다.
— Matthieu M.

42
어리석은 래퍼를 작성하는 것 외에는 생성자로 해결할 수없는 좋은 이유는 C ++ 사용 여부에 관계없이 대부분의 실제 API는 C ++가 아니라 C이며, 그중 몇 개도 설정하려는 구조를 제공하지 않기 때문입니다. 하나 또는 두 개의 필드 (첫 번째 필드는 아님)이지만 나머지는 0으로 초기화해야합니다. Win32 API OVERLAPPED가 그러한 예입니다. 작성할 수 있으면 ={.Offset=12345};코드가 훨씬 더 명확 해지고 오류 발생 가능성이 줄어 듭니다. BSD 소켓도 비슷한 예입니다.
— Damon

14
의 코드 main는 합법적 인 C99가 아닙니다. 읽어야합니다 struct Person p = { .age = 18 };
— chqrlie

14
FYI C ++ 20은 지정된 이니셜 라이저를 지원합니다
— Andrew Tomazos 2018

답변:


34

C ++에는 생성자가 있습니다. 하나의 멤버 만 초기화하는 것이 합리적이면 적절한 생성자를 구현하여 프로그램에서 표현할 수 있습니다. 이것은 C ++이 촉진하는 일종의 추상화입니다.

반면에 지정된 이니셜 라이저 기능은 클라이언트 코드에서 멤버를 직접 노출하고 쉽게 액세스 할 수 있도록하는 것입니다. 이로 인해 18 세 (세?)이지만 키와 몸무게가 0 인 사람이 생깁니다.


즉, 지정된 이니셜 라이저는 내부가 노출되는 프로그래밍 스타일을 지원하며 클라이언트는 유형을 사용하려는 방식을 유연하게 결정할 수 있습니다.

C ++는 유형 의 디자이너 측에 유연성을 부여하는 데 더 관심이 있으므로 디자이너는 유형을 올바르게 사용하기 쉽고 잘못 사용하기 어렵게 만들 수 있습니다. 디자이너가 형식을 초기화 할 수있는 방법을 제어하는 ​​것은 디자이너가 생성자, 클래스 내 이니셜 라이저 등을 결정합니다.


12
C ++에 지정된 이니셜 라이저가없는 이유에 대한 참조 링크를 표시하십시오. 그 제안을 본 적이 없었습니다.
— Johannes Schaub-litb

20
Person작성자가 사용자가 멤버를 설정하고 초기화 할 수있는 최대한의 유연성을 제공하기를 원했기 때문에 생성자를 제공하지 않은 바로 그 이유가 아닙니까? 사용자는 이미 쓸 수도 있습니다 Person p = { 0, 0, 18 };.
— Johannes Schaub-litb

7
비슷한 것이 최근 open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3605.html에 의해 C ++ 14 사양에 포함 되었습니다 .
— Johannes Schaub-litb

4
@ JohannesSchaub-litb 순전히 기계적이고 근접한 원인에 대해 말하는 것이 아닙니다 (즉,위원회에 제안되지 않았습니다). 나는 내가 지배적 인 요소라고 믿는 것을 설명하고있다. — Person매우 C 디자인이므로 C 기능이 의미가있을 수 있습니다. 그러나 C ++는 지정된 이니셜 라이저가 필요없는 더 나은 디자인을 가능하게합니다. — 내 관점에서 집계에 대한 클래스 내 이니셜 라이저에 대한 제한을 제거하는 것은 지정된 이니셜 라이저보다 C ++의 정신에 훨씬 더 부합합니다.
— bames53 2013 년

4
이에 대한 C ++ 대체는 명명 된 함수 인수 일 수 있습니다. 그러나 현재로서는 이름 인수가 공식적으로 존재하지 않습니다. 이에 대한 제안은 N4172 명명 된 인수 를 참조하십시오 . 코드를 오류 발생 가능성이 적고 읽기 쉽게 만들 수 있습니다.
— David Baird 2015

89

'17 년 7 월 15 일 P0329R4 는표준 : http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
이 제한적으로 지원을 제공합니다의 지정된 이니셜 라이저. 이 제한은 다음과 같이 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 ++에서 유효하지 않습니다.

에 대한 이전 Boost는 실제로 지정된 Intializer를 지원합니다. 지원을 제공했으며 지원을 추가하기위한 수많은 제안이있었습니다.표준, 예 : n4172 및 Daryle Walker의 이니셜 라이저에 지정 추가 제안 . 제안은 다음의 구현을 인용합니다.의 Visual C ++, gcc 및 Clang의 지정된 이니셜 라이저는 다음과 같이 주장합니다.

우리는 변경 사항을 구현하기가 비교적 간단 할 것이라고 믿습니다.

그러나 표준위원회 는 다음 과 같이 이러한 제안을 반복해서 거부합니다 .

EWG는 제안 된 접근 방식에서 다양한 문제를 발견했으며 여러 번 시도하고 실패 할 때마다 문제 해결을 시도하는 것이 타당하다고 생각하지 않았습니다.

Ben Voigt의 의견 은이 접근 방식에서 극복 할 수없는 문제를 보는 데 도움이되었습니다. 주어진:

struct X {
    int c;
    char a;
    float b;
};

이 함수는 어떤 순서로 호출됩니까? : struct X foo = {.a = (char)f(), .b = g(), .c = h()} ? 놀랍게도:

이니셜 라이저에서 하위 표현식의 평가 순서는 불확실하게 순서가 지정됩니다 [ 1 ]

(Visual C ++, gcc 및 Clang은 모두 다음 순서로 호출하므로 동의 한 동작을 보이는 것 같습니다.)

  1. h()
  2. f()
  3. g()

그러나 표준의 불확실한 특성은 이러한 함수에 상호 작용이있는 경우 결과 프로그램 상태도 불확실 하고 컴파일러가 경고하지 않을 것임을 의미합니다 . 지정된 이니셜 라이저가 잘못 작동하는 경우 경고를받을 수 있는 방법이 있습니까?

않는 엄격한 초기화 목록의 요구 사항이 11.6.4 [dcl.init.list] 4 :

braced-init-list의 initializer-list 내에서, 팩 확장 (17.5.3)의 결과를 포함하는 initializer-clauses는 나타나는 순서대로 평가됩니다. 즉, 주어진 initializer-clause와 관련된 모든 값 계산과 부수 효과는 initializer-list의 쉼표로 구분 된 목록에서 그 뒤에 오는 모든 initializer-clause와 관련된 모든 값 계산 및 부작용보다 먼저 시퀀싱됩니다.

그래서 지원을 받으려면 다음 순서대로 실행해야합니다.

  1. f()
  2. g()
  3. h()

이전과의 호환성 깨기 구현.
위에서 논의한 바와 같이,이 문제는 허용 된 지정 이니셜 라이저에 대한 제한으로 인해 우회되었습니다.. 표준화 된 동작을 제공하여 지정된 이니셜 라이저의 실행 순서를 보장합니다.


3
물론이 코드에서는에 struct X { int c; char a; float b; }; X x = { .a = f(), .b = g(), .c = h() };대한 호출 h()이 f()또는 이전에 수행됩니다 g(). 의 정의 struct X가 근처에 없다면 이것은 매우 놀랄 것입니다. 이니셜 라이저 표현식이 부작용이 없어야하는 것은 아닙니다.
— Ben Voigt 2015 년

2
물론 이것은 새로운 것이 아니며 ctor 멤버 초기화에는 이미이 문제가 있지만 클래스 멤버의 정의에 있으므로 긴밀한 결합은 놀라운 일이 아닙니다. 그리고 지정된 이니셜 라이저는 ctor 멤버-이니셜 라이저가 할 수있는 방식으로 다른 멤버를 참조 할 수 없습니다.
— Ben Voigt 2015 년

2
@MattMcNabb : 아니요, 더 극단적 인 것은 아닙니다. 그러나 클래스 생성자를 구현하는 개발자가 멤버 선언 순서를 알기를 기대합니다. 클래스의 소비자는 완전히 다른 프로그래머 일 수 있습니다. 요점은 멤버의 순서를 조회하지 않고 초기화를 허용하는 것이므로 제안서에 치명적인 결함으로 보입니다. 지정된 이니셜 라이저는 생성되는 객체를 참조 할 수 없기 때문에 첫 번째 인상은 초기화 표현식을 지정 순서로 먼저 평가 한 다음 선언 순서로 멤버 초기화를 평가할 수 있다는 것입니다. 하지만 ...
— Ben Voigt 2015 년

2
@JonathanMee : 음, 다른 질문은 다음과 같이 대답했습니다. C99 집계 이니셜 라이저는 순서가 지정되지 않았으므로 지정된 이니셜 라이저가 정렬 될 것으로 기대하지 않습니다. C ++ braced-init-lists 순서가 지정되고 지정된 초기화 프로그램에 대한 제안은 잠재적으로 놀라운 순서를 사용합니다 (모든 braced-init 목록에 사용되는 어휘 순서와 ctor-initializer에 사용되는 멤버 순서와 모두 일치 할 수 없습니다. -lists)
— Ben Voigt

3
Jonathan : "C ++ 지원은이 작업이 [...] 이전 c99 구현과의 호환성을 깨는 순서대로 실행되어야했습니다." 이건 모르겠어요, 죄송합니다. 순서는 C99에서 불확실한 경우 1. 다음 분명히 어떤 실제 순서는 임의의 C ++의 선택을 포함하여, 잘해야한다. b) des를 지원하지 않습니다. 이니셜 라이저는 이미 C99 호환성을 훨씬 더
— 깨뜨립니다.

34

약간의 해커 리이므로 재미로 공유하십시오.

#define with(T, ...)\
    ([&]{ T ${}; __VA_ARGS__; return $; }())

그리고 다음과 같이 사용하십시오.

MyFunction(with(Params,
    $.Name = "Foo Bar",
    $.Age  = 18
));

다음으로 확장됩니다.

MyFunction(([&] {
 Params ${};
 $.Name = "Foo Bar", $.Age = 18;
 return $;
}()));

Neat, $type 이라는 변수로 람다를 만들고 T반환하기 전에 멤버를 직접 할당합니다. 맵시 있는. 성능 문제가 있는지 궁금합니다.
— TankorSmash

1
최적화 된 빌드에서는 람다 나 그 호출의 흔적을 볼 수 없습니다. 모두 인라인됩니다.
— keebus

1
나는이 대답을 절대적으로 좋아합니다.
— Reed

6
와. $가 유효한 이름인지조차 몰랐습니다.
— Chris Watts

레거시 C 컴파일러에서 지원했으며 이전 버전과의 호환성을 위해 지원되었습니다.
— keebus

22

지정된 이니셜 라이저는 현재 C ++ 20 작업 본문에 포함되어 있습니다. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf 그래서 마침내 볼 수 있습니다!


3
그러나 제한이 있음을 유의하십시오 . C ++에서는 지정된 초기화 지원이 C의 해당 기능에 비해 제한됩니다. C ++에서 비 정적 데이터 멤버에 대한 지정자는 선언 순서에 따라 지정되어야하며 배열 요소 및 중첩 지정자는 지정되지 않습니다. 지원되며 지정된 이니셜 라이저와 지정되지 않은 이니셜 라이저는 동일한 이니셜 라이저 목록에서 혼합 될 수 없습니다. 이는 특히 열거 형 키 조회 테이블 을 쉽게 만들 수 없다는 것을 의미 합니다 .
— Ruslan

@Ruslan : C ++이 왜 그렇게 제한했는지 궁금합니다. 항목의 값이 평가 및 / 또는 구조체에 기록되는 순서가 항목이 초기화 목록에 지정된 순서와 일치하는지 또는 멤버가 구조체에 나타나는 순서와 일치하는지에 대해 혼란이있을 수 있음을 이해합니다. 이에 대한 해결책은 단순히 초기화 표현식이 임의의 순서로 실행되고 초기화가 완료 될 때까지 개체의 수명이 시작되지 않는다는 &것입니다 ( 연산자는 개체 가 수명 동안 가질 주소를 반환합니다 ).
— supercat

5

C ++ 11에없는 두 가지 핵심 C99 기능 은 "지정된 이니셜 라이저 및 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.x0이 0이라는 것을 알았 으므로 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실제로 컴파일 시간 상수 값이 아님을 알 수 있습니다 .


8
이제 시도하십시오 constexpr point(int _x,int _y):x(_x),y(_y){}. clang ++의 최적화 프로그램은 코드에서 비교를 제거하는 것 같습니다. 따라서 이것은 QoI 문제 일뿐입니다.
— dyp

또한 내부 연결이있는 경우 전체 a_point 개체가 최적화 될 것으로 예상합니다. 즉, 익명 네임 스페이스에 넣고 무슨 일이 일어나는지 확인하십시오. goo.gl/wNL0HC
— Arvid

@dyp : 생성자를 정의하는 것조차도 유형이 제어 할 수있는 경우에만 가능합니다. 당신은, 예를 들어, 그렇게 할 수 없습니다 struct addrinfo또는 struct sockaddr_in할당이 선언에서 분리 당신이 남아있어, 그래서.
— musiphil

2
@musiphil 적어도 C ++ 14에서 이러한 C 스타일 구조체는 할당을 사용하여 constexpr 함수에서 지역 변수로 적절하게 설정 한 다음 해당 함수에서 반환 할 수 있습니다. 또한 내 요점은 최적화를 허용하는 C ++ 생성자의 대체 구현을 보여주는 것이 아니라 초기화 형식이 다른 경우 컴파일러가이 최적화를 수행 할 수 있음을 보여주는 것입니다. 컴파일러가 "충분히 좋은"경우 (즉, 이러한 형태의 최적화를 지원함), ctor 또는 지정된 이니셜 라이저를 사용하는지, 아니면 다른 것을 사용하는지는 관련이 없습니다.
— dyp
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.