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를 지원합니다. 지원을 제공했으며 지원을 추가하기위한 수많은 제안이있었습니다.표준, 예 : n4172Daryle 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.