집계 초기화 멤버가 누락되지 않도록 할 수 있습니까?


43

같은 유형의 많은 멤버가있는 구조체가 있습니다.

struct VariablePointers {
   VariablePtr active;
   VariablePtr wasactive;
   VariablePtr filename;
};

문제는 구조체 멤버 중 하나 (예 :)를 초기화하는 것을 잊어 버린 경우 wasactive다음과 같습니다.

VariablePointers{activePtr, filename}

컴파일러는 그것에 대해 불평하지 않지만 부분적으로 초기화 된 하나의 객체를 갖게됩니다. 이런 종류의 오류를 어떻게 방지 할 수 있습니까? 생성자를 추가 할 수는 있지만 변수 목록을 두 번 복제 하므로이 세 가지를 모두 입력해야합니다!

C ++ 11에 대한 솔루션이있는 경우 C ++ 11 답변을 추가하십시오 (현재 해당 버전으로 제한되어 있음). 최신 언어 표준도 환영합니다!


6
생성자를 입력해도 끔찍하게 들리지 않습니다. 멤버가 너무 많지 않은 경우 리팩토링이 순서대로되어있을 수 있습니다.
Gonen I

1
@Someprogrammerdude 나는 그가 실수는 당신이 실수로 초기화 값을 생략 할 수 있다는 것을 의미한다고 생각합니다
Gonen I

2
@theWiseBro 배열 / 벡터가 어떻게 도움이되는지 알고 있다면 답변을 게시해야합니다. 그것은 분명하지 않다, 나는 그것을 보지 않는다
idclev 463035818 12

2
@Someprogrammerdude 그러나 그것은 심지어 경고입니까? VS2019에서는 볼 수 없습니다.
acraig5075

8
-Wmissing-field-initializers컴파일 플래그.
Ron

답변:


42

필요한 이니셜 라이저가없는 경우 링커 오류를 트리거하는 트릭이 있습니다.

struct init_required_t {
    template <class T>
    operator T() const; // Left undefined
} static const init_required;

용법:

struct Foo {
    int bar = init_required;
};

int main() {
    Foo f;
}

결과:

/tmp/ccxwN7Pn.o: In function `Foo::Foo()':
prog.cc:(.text._ZN3FooC2Ev[_ZN3FooC5Ev]+0x12): undefined reference to `init_required_t::operator int<int>() const'
collect2: error: ld returned 1 exit status

주의 사항 :

  • C ++ 14 이전에는 Foo집계가 전혀되지 않습니다.
  • 이것은 기술적으로 정의되지 않은 동작 (ODR 위반)에 의존하지만 모든 플랫폼에서 작동합니다.

변환 연산자를 삭제할 수 있으며 컴파일러 오류입니다.
jrok

@jrok 예, Foo실제로 연산자를 호출하지 않더라도 선언 된 즉시 하나 입니다.
Quentin

2
@jrok 그러나 초기화가 제공 되어도 컴파일되지 않습니다. godbolt.org/z/yHZNq_ 부록 : MSVC의 경우 설명 된대로 작동합니다. godbolt.org/z/uQSvDa 버그입니까?
n314159

물론, 바보
jrok

6
불행히도이 트릭은 C ++ 11과 함께 작동하지 않습니다. 왜냐하면 집계가되지 않기 때문입니다. 가능하다면 C ++ 11 솔루션이 여전히 선호됩니다
Johannes Schaub-

22

clang 및 gcc의 경우 -Werror=missing-field-initializers누락 된 필드 이니셜 라이저에 대한 경고를 오류로 설정하여 컴파일 할 수 있습니다 .갓 볼트

편집 : MSVC의 경우 level에서도 경고가 발생 /Wall하지 않는 것 같습니다. 따라서이 컴파일러로 누락 된 이니셜 라이저에 대해 경고 할 수는 없다고 생각합니다. 갓 볼트


7

우아하고 편리한 솔루션은 아니지만 C ++ 11에서도 작동하며 컴파일 타임 (링크 타임이 아님) 오류가 발생한다고 가정합니다.

아이디어는 기본 초기화없이 유형의 마지막 멤버에서 추가 멤버를 구조체에 추가하는 것입니다 (type 값으로 초기화 할 수 없음) VariablePtr (또는 이전 값의 유형은 무엇이든))

예를 들어

struct bar
 {
   bar () = delete;

   template <typename T> 
   bar (T const &) = delete;

   bar (int) 
    { }
 };

struct foo
 {
   char a;
   char b;
   char c;

   bar sentinel;
 };

이렇게하면 집계 초기화 목록에 모든 요소를 ​​추가하거나 마지막 값을 명시 적으로 초기화하는 값 ( sentinel예 :에 대한 정수 )을 포함 시키거나 "삭제 된 생성자 'bar'에 대한 호출"오류가 발생합니다.

그래서

foo f1 {'a', 'b', 'c', 1};

컴파일하고

foo f2 {'a', 'b'};  // ERROR

하지 않습니다.

불행히도

foo f3 {'a', 'b', 'c'};  // ERROR

컴파일하지 않습니다.

-- 편집하다 --

MSalters (감사)가 지적한 것처럼 원래 예제에는 결함 (또 다른 결함) bar이 있습니다 . 값은 값으로 초기화 될 수 있으므로 char(다음으로 변환 가능 int) 다음 초기화가 작동합니다

foo f4 {'a', 'b', 'c', 'd'};

이것은 매우 혼란 스러울 수 있습니다.

이 문제를 피하기 위해 다음 삭제 된 템플릿 생성자를 추가했습니다.

 template <typename T> 
 bar (T const &) = delete;

따라서 위의 f4선언 d은 삭제 된 템플릿 생성자가 값을 가로 채기 때문에 컴파일 오류를 발생시킵니다.


고마워요, 이거 좋네요! 언급 한 것처럼 완벽하지는 않으며 foo f;컴파일하지 못하지만이 트릭의 결함보다 더 많은 기능 일 수 있습니다. 이보다 더 나은 제안이 없으면 수락합니다.
Johannes Schaub-

1
바 생성자는 가독성을 위해 init_list_end와 같은 const 중첩 클래스 멤버를 허용합니다.
Gonen I

@GonenI-가독성을 위해 값을 받아들이고 그 값을 enum이름 init_list_end(o 간단히 list_end)으로 지정할 수 있습니다 enum. 그러나 가독성은 많은 타이프 라이팅을 추가하므로 추가 값 이이 답변의 약점이라는 점을 감안할 때 그것이 좋은 아이디어인지는 모르겠습니다.
max66

constexpr static int eol = 0;의 헤더 와 같은 것을 추가하십시오 bar. test{a, b, c, eol}나에게 읽기 쉬운 것 같습니다.
n314159

@ n314159 - 음 ...이 될 bar::eol; 그것은 거의 enum가치 를 전달합니다 . 그러나 나는 그것이 중요하다고 생각하지 않습니다 : 대답의 핵심은 "기본 초기화없이 유형의 추가 멤버를 마지막 위치에 추가하십시오"; 이 bar부분은 솔루션이 작동 함을 보여주는 간단한 예일뿐입니다. 정확한 "기본 초기화없는 유형"은 상황 (IMHO)에 따라 달라집니다.
max66

4

들어 CppCoreCheck 정확히 확인하기위한 규칙이있다, 모든 멤버가 초기화되고 그 오류에 경고에서 설정 될 수 있다면 - 물론 일반적으로 프로그램 전체이다.

최신 정보:

확인하려는 규칙은 typesafety의 일부입니다 Type.6.

유형 6 : 항상 멤버 변수 초기화 : 기본 생성자 또는 기본 멤버 이니셜 라이저를 사용하여 항상 초기화하십시오.


2

가장 간단한 방법은 멤버 유형에 인수 없음 생성자를 제공하지 않는 것입니다.

struct B
{
    B(int x) {}
};
struct A
{
    B a;
    B b;
    B c;
};

int main() {

        // A a1{ 1, 2 }; // will not compile 
        A a1{ 1, 2, 3 }; // will compile 

다른 옵션 : 멤버가 const & 인 경우 모든 멤버를 초기화해야합니다.

struct A {    const int& x;    const int& y;    const int& z; };

int main() {

//A a1{ 1,2 };  // will not compile 
A a2{ 1,2, 3 }; // compiles OK

하나의 더미 const & member와 함께 살 수 있다면 @ max66의 센티넬 아이디어와 결합 할 수 있습니다.

struct end_of_init_list {};

struct A {
    int x;
    int y;
    int z;
    const end_of_init_list& dummy;
};

    int main() {

    //A a1{ 1,2 };  // will not compile
    //A a2{ 1,2, 3 }; // will not compile
    A a3{ 1,2, 3,end_of_init_list() }; // will compile

cppreference https://en.cppreference.com/w/cpp/language/aggregate_initialization에서

이니셜 라이저 절 수가 멤버 수보다 적거나 이니셜 라이저 목록이 완전히 비어있는 경우 나머지 멤버는 값으로 초기화됩니다. 참조 유형의 구성원이 나머지 구성원 중 하나 인 경우 프로그램이 잘못 구성됩니다.

또 다른 옵션은 max66의 센티넬 아이디어를 취하고 가독성을 위해 구문 설탕을 추가하는 것입니다.

struct init_list_guard
{
    struct ender {

    } static const end;
    init_list_guard() = delete;

    init_list_guard(ender e){ }
};

struct A
{
    char a;
    char b;
    char c;

    init_list_guard guard;
};

int main() {
   // A a1{ 1, 2 }; // will not compile 
   // A a2{ 1, init_list_guard::end }; // will not compile 
   A a3{ 1,2,3,init_list_guard::end }; // compiles OK

불행히도, 이것은 A움직일 수 없게 만들고 복사 의미를 변경합니다 ( A더 이상 말하면 값의 집합이 아닙니다) :(
Johannes Schaub-litb

@ JohannesSchaub-litb OK. 편집 된 답변 에서이 아이디어는 어떻습니까?
Gonen I

@ JohannesSchaub-litb : 마찬가지로, 첫 번째 버전은 멤버 포인터를 만들어 간접적 인 수준을 추가합니다. 더욱 중요한 것은 참조해야 하는 일, 그리고 1,2,3개체를 효과적으로 범위 때 함수 끝의 외출 자동 스토리지에 지역 주민이다. 그리고 x86-64와 같은 64 비트 포인터가있는 시스템에서 3 대신에 sizeof (A) 24를 만듭니다.
Peter Cordes

더미 참조는 크기를 3 바이트에서 16 바이트로 증가시킵니다 (포인터 (참조) 멤버의 정렬을위한 패딩 + 포인터 자체). 참조를 절대 사용하지 않는 한, 그것이 사라진 객체를 가리키는 경우에는 괜찮을 것입니다 범위. 나는 그것이 최적화되지 않는 것에 대해 걱정할 것이고, 그것을 복사하는 것이 확실하지 않을 것입니다. (빈 클래스는 크기가 아닌 다른 것을 최적화 할 가능성이 더 높으므로 여기에서 세 번째 옵션은 가장 나쁘지만 적어도 일부 ABI에서는 모든 개체의 공간이 여전히 필요합니다. 패딩에 대한 걱정도 여전히 남아 있습니다. 경우에 따라 최적화.)
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.