집계 및 POD 란 무엇이며 어떻게 / 왜 특별합니까?


548

FAQ 는 집계 및 POD에 관한 것으로 다음 자료를 다룹니다.

  • 집계 란 무엇입니까 ?
  • 무엇 POD 의 (일반 오래된 데이터)?
  • 그들은 어떻게 관련되어 있습니까?
  • 어떻게 그리고 왜 특별합니까?
  • C ++ 11의 변경 사항은 무엇입니까?


POD == memcpy'able, Aggregate == Aggregate-initializable?
Ofek Shilon

답변:


571

읽는 방법:

이 기사는 다소 길다. 집계와 POD (Plain Old Data)에 대해 알고 싶다면 시간이 걸리고 읽어보십시오. 집계에만 관심이 있다면 첫 번째 부분 만 읽으십시오. 당신은 단지 포드에 관심이 있다면 당신은 첫째 집계의 정의, 의미 및 예제를 읽을 수 있어야합니다 그리고 당신은 할 수 있습니다 포드로 이동하지만, 난 여전히 전체의 첫 부분을 읽어 보시기 바랍니다 것입니다. 집계 개념은 POD를 정의하는 데 필수적입니다. 문법, 문체, 형식, 구문 등 사소한 오류라도 발견되면 의견을 남겨주세요. 편집하겠습니다.

이 답변은 C ++ 03에 적용됩니다. 다른 C ++ 표준은 다음을 참조하십시오.

집계 란 무엇이고 왜 특별한가

C ++ 표준 ( C ++ 03 8.5.1 §1 )의 공식 정의 :

집합은 사용자 선언 생성자 (12.1), 비공개 또는 보호 된 비 정적 데이터 멤버 (절 11), 기본 클래스 (절 10) 및 가상 함수 (10.3)가없는 배열 또는 클래스 (9 절)입니다. ).

자,이 정의를 파싱 해 봅시다. 우선, 모든 배열은 집계입니다. 다음과 같은 경우 클래스도 집합체가 될 수 있습니다. 구조체 또는 공용체에 대해서는 아무 것도 말하지 않습니다. 집계 할 수 없습니까? 네, 그들은 할 수 있어요. C ++ class에서이 용어 는 모든 클래스, 구조체 및 공용체를 나타냅니다. 따라서 클래스 (또는 구조체 또는 공용체)는 위 정의의 기준을 충족하는 경우에만 집계입니다. 이러한 기준은 무엇을 의미합니까?

  • 이것은 집계 클래스가 생성자를 가질 수 없다는 것을 의미하지는 않습니다. 실제로 기본 생성자 및 / 또는 복사 생성자가 컴파일러에 의해 암시 적으로 선언되고 사용자가 명시 적으로 선언하지 않는 한 생성자가있을 수 있습니다

  • 비공개 또는 보호 된 비 정적 데이터 멤버가 없습니다 . 원하는만큼 개인 및 보호 멤버 함수 (생성자는 아님)뿐만 아니라 개인 또는 보호 된 정적 데이터 멤버 및 멤버 함수를 가질 수 있으며 집계 클래스 규칙을 위반하지 않을 수 있습니다

  • 집계 클래스에는 사용자 선언 / 사용자 정의 사본 할당 연산자 및 / 또는 소멸자가있을 수 있습니다.

  • 배열은 집계되지 않은 클래스 유형의 배열 인 경우에도 집계입니다.

이제 몇 가지 예를 살펴 보겠습니다.

class NotAggregate1
{
  virtual void f() {} //remember? no virtual functions
};

class NotAggregate2
{
  int x; //x is private by default and non-static 
};

class NotAggregate3
{
public:
  NotAggregate3(int) {} //oops, user-defined constructor
};

class Aggregate1
{
public:
  NotAggregate1 member1;   //ok, public member
  Aggregate1& operator=(Aggregate1 const & rhs) {/* */} //ok, copy-assignment  
private:
  void f() {} // ok, just a private function
};

당신은 아이디어를 얻습니다. 이제 집계가 어떻게 특별한 지 봅시다. 집계되지 않은 클래스와 달리 중괄호로 초기화 할 수 있습니다 {}. 이 초기화 구문은 일반적으로 배열로 알려져 있으며 우리는 이것이 집합이라는 것을 배웠습니다. 이제부터 시작하겠습니다.

Type array_name[n] = {a1, a2, …, am};

(m == N)의 경우,
제 i 번째 배열 요소가 초기화되는 I
다른 경우 (m <n)의
어레이의 제 m 소자는으로 초기화되는 하나 하는 2 ...하는 m 다른n - m요소 가능한 경우 값이 초기화됩니다 (아래 용어 설명에 대해서는 아래를 참조하십시오)
else if (m> n)
컴파일러가
다른 오류를 발생시킵니다 (n이 전혀 지정되지 않은 경우 int a[] = {1, 2, 3};)
. 어레이 (N) 따라서, m 동일한 것으로 가정int a[] = {1, 2, 3};동등int a[3] = {1, 2, 3};

스칼라 타입 (객체의 경우 bool, int, char, double, 포인터 등) 인 값으로 초기화 는이 초기화되는 수단 0해당 유형 ( falsebool, 0.0double등). 사용자가 선언 한 기본 생성자가있는 클래스 유형의 객체가 값으로 초기화되면 기본 생성자가 호출됩니다. 기본 생성자가 내재적으로 정의 된 경우 모든 비 정적 멤버는 재귀 적으로 값으로 초기화됩니다. 이 정의는 부정확하고 약간 부정확하지만 기본 아이디어를 제공해야합니다. 참조는 값을 초기화 할 수 없습니다. 예를 들어, 클래스에 적절한 기본 생성자가 없으면 집계되지 않은 클래스의 값 초기화가 실패 할 수 있습니다.

배열 초기화의 예 :

class A
{
public:
  A(int) {} //no default constructor
};
class B
{
public:
  B() {} //default constructor available
};
int main()
{
  A a1[3] = {A(2), A(1), A(14)}; //OK n == m
  A a2[3] = {A(2)}; //ERROR A has no default constructor. Unable to value-initialize a2[1] and a2[2]
  B b1[3] = {B()}; //OK b1[1] and b1[2] are value initialized, in this case with the default-ctor
  int Array1[1000] = {0}; //All elements are initialized with 0;
  int Array2[1000] = {1}; //Attention: only the first element is 1, the rest are 0;
  bool Array3[1000] = {}; //the braces can be empty too. All elements initialized with false
  int Array4[1000]; //no initializer. This is different from an empty {} initializer in that
  //the elements in this case are not value-initialized, but have indeterminate values 
  //(unless, of course, Array4 is a global array)
  int array[2] = {1, 2, 3, 4}; //ERROR, too many initializers
}

이제 중괄호로 집계 클래스를 초기화하는 방법을 살펴 보겠습니다. 거의 같은 방식입니다. 배열 요소 대신 정적이 아닌 데이터 멤버를 클래스 정의에 나타나는 순서대로 초기화합니다 (모두 정의에 의해 모두 공개됨). 멤버보다 이니셜 라이저가 적은 경우 나머지는 값으로 초기화됩니다. 명시 적으로 초기화되지 않은 멤버 중 하나의 값을 초기화 할 수 없으면 컴파일 타임 오류가 발생합니다. 필요한 것보다 많은 이니셜 라이저가 있으면 컴파일 타임 오류도 발생합니다.

struct X
{
  int i1;
  int i2;
};
struct Y
{
  char c;
  X x;
  int i[2];
  float f; 
protected:
  static double d;
private:
  void g(){}      
}; 

Y y = {'a', {10, 20}, {20, 30}};

위의 예제 y.c에서 'a', y.x.i1with 10, y.x.i2with 20, y.i[0]with 20, y.i[1]with 30및 with y.f값으로 초기화되며 값으로 초기화됩니다 0.0. 보호 된 정적 멤버 d는이므로 초기화되지 않습니다 static.

집계 공용체는 첫 번째 멤버 만 중괄호로 초기화 할 수 있다는 점에서 다릅니다. C ++에서 노조 사용을 고려할 정도로 충분히 진보 된 경우 (사용이 매우 위험 할 수 있으며 신중하게 고려해야 함) 표준에서 노조 규칙을 직접 찾을 수 있다고 생각합니다. :)

이제 집계에 대한 특별한 점을 알았으므로 클래스의 제한 사항을 이해해 봅시다. 즉, 왜 거기에 있습니다. 중괄호를 사용하여 멤버 단위로 초기화하면 클래스가 멤버의 합계에 지나지 않는다는 것을 의미합니다. 사용자 정의 생성자가 있으면 멤버를 초기화하기 위해 추가 작업을 수행해야하므로 중괄호 초기화가 올바르지 않습니다. 가상 함수가 존재한다면,이 클래스의 객체는 (대부분의 구현에서) 생성자에 설정된 클래스의 vtable에 대한 포인터를 가지므로 괄호 초기화가 불충분합니다. 연습과 비슷한 방식으로 나머지 제한을 알아낼 수 있습니다 :).

집계에 대해서는 충분합니다. 이제 더 엄격한 유형의 세트를 정의 할 수 있습니다.

POD 란 무엇이며 왜 특별합니까?

C ++ 표준 ( C ++ 03 9 §4 )의 공식 정의 :

POD-struct는 non-POD-struct, non-POD-union (또는 이러한 유형의 배열) 또는 참조 유형의 비 정적 데이터 멤버가없고 사용자 정의 사본 할당 연산자가없고, 사용자 정의 소멸자. 마찬가지로, POD 연합은 non-POD-struct 유형, non-POD-union (또는 이러한 유형의 배열) 또는 참조의 비 정적 데이터 멤버가없고 사용자 정의 사본 할당 연산자가없는 집계 공용체입니다. 그리고 사용자 정의 소멸자가 없습니다. POD 클래스는 POD 구조체 또는 POD 연합 인 클래스입니다.

와우, 파싱하기가 더 힘들지 않나요? :) 위와 같은 이유로 노조를 제외하고 좀 더 명확한 방식으로 문구를 바꾸겠습니다.

집계 정의 클래스는 사용자 정의 사본 할당 연산자 및 소멸자가없고 비 정적 구성원이 비 POD 클래스, 비 POD 배열 또는 참조가 아닌 경우 POD라고합니다.

이 정의는 무엇을 의미합니까? ( PODPlain Old Data를 의미 한다고 언급 했습니까 ?)

  • 모든 POD 클래스는 집계이거나 클래스를 집계하지 않으면 POD가 아닌 것입니다.
  • 표준 용어가 두 경우 모두 POD 구조이지만 구조체와 마찬가지로 클래스는 POD가 될 수 있습니다.
  • 집계의 경우와 마찬가지로 클래스에 어떤 정적 멤버가 있는지는 중요하지 않습니다.

예 :

struct POD
{
  int x;
  char y;
  void f() {} //no harm if there's a function
  static std::vector<char> v; //static members do not matter
};

struct AggregateButNotPOD1
{
  int x;
  ~AggregateButNotPOD1() {} //user-defined destructor
};

struct AggregateButNotPOD2
{
  AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};

POD 클래스, POD 연합, 스칼라 유형 및 이러한 유형의 배열을 통칭하여 POD 유형 이라고 합니다.
POD는 여러면에서 특별합니다. 몇 가지 예만 제공하겠습니다.

  • POD 클래스는 C 구조체에 가장 가깝습니다. 그것들과는 달리, POD는 멤버 함수와 임의의 정적 멤버를 가질 수 있지만,이 둘 중 어느 것도 오브젝트의 메모리 레이아웃을 변경하지 않습니다. 따라서 C 및 .NET에서 사용할 수있는 이식성이 뛰어난 동적 라이브러리를 작성하려면 내 보낸 모든 함수가 POD 유형의 매개 변수 만 가져오고 리턴하도록해야합니다.

  • 비 POD 클래스 유형의 객체 수명은 생성자가 완료되면 시작되고 소멸자가 완료되면 종료됩니다. POD 클래스의 경우 수명은 오브젝트의 스토리지가 점유 될 때 시작되고 해당 스토리지가 해제되거나 재사용 될 때 완료됩니다.

  • POD 유형의 객체의 경우 표준 memcpy에 따라 객체의 내용을 char 또는 unsigned char 배열에 넣은 다음 memcpy내용을 다시 객체에 넣을 때 객체의 원래 값이 유지됩니다. 비 POD 유형의 객체에 대해서는 그러한 보장이 없습니다. 또한을 사용하여 POD 객체를 안전하게 복사 할 수 있습니다 memcpy. 다음 예제는 T가 POD 유형이라고 가정합니다.

    #define N sizeof(T)
    char buf[N];
    T obj; // obj initialized to its original value
    memcpy(buf, &obj, N); // between these two calls to memcpy,
    // obj might be modified
    memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
    // holds its original value
  • 고토 진술. 아시다시피, 일부 변수가 아직 범위에 있지 않은 지점에서 이미 범위에있는 지점으로 이동하여 이동하는 것은 불법입니다 (컴파일러가 오류를 발행해야 함). 이 제한 사항은 변수가 비 POD 유형 인 경우에만 적용됩니다. 다음 예에서는 형식 f()이 잘못되었지만 형식 g()이 잘못 되었습니다. Microsoft의 컴파일러는이 규칙에 너무 자유롭기 때문에 두 경우 모두 경고를 표시합니다.

    int f()
    {
      struct NonPOD {NonPOD() {}};
      goto label;
      NonPOD x;
    label:
      return 0;
    }
    
    int g()
    {
      struct POD {int i; char c;};
      goto label;
      POD x;
    label:
      return 0;
    }
  • POD 객체의 시작 부분에 패딩이없는 것이 보장됩니다. 포드-클래스 A의 첫 번째 멤버는 타입 T의 경우 즉, 안전하게 할 수 reinterpret_cast에서 A*까지 T*포인터를 얻을 반대로 제 1 부재와 그 반대합니다.

목록은 계속 이어지고…

결론

아시다시피 많은 언어 기능이 다르게 작동하기 때문에 POD가 정확히 무엇인지 이해하는 것이 중요합니다.


3
좋은 대답입니다. 주석 : "기본 생성자가 내재적으로 정의 된 경우 모든 비 정적 멤버는 재귀 적으로 값으로 초기화됩니다." "예를 들어 클래스에 적절한 기본 생성자가 없으면 집계되지 않은 클래스의 값 초기화에 실패 할 수 있습니다." 정확하지 않음 : 암시 적으로 선언 된 기본 생성자 를 사용하여 클래스의 값을 초기화 할 때 암시 적으로 정의 된 기본 생성자가 필요하지 않습니다. 따라서 주어진 ( private:적절하게 삽입 ) : struct A { int const a; };그러면 기본 생성자 정의가 잘못 구성 A()되어 있어도 올바르게 A구성됩니다.
Johannes Schaub-litb

4
@Kev : 동일한 정보를 더 짧은 답변으로 묶을 수 있다면 모두 기쁜 마음으로 투표 할 것입니다!
sbi

3
@Armen은 또한 동일한 질문에 대해 여러 답변을 할 수 있습니다. 각 답변에는 질문에 대한 해결책의 일부가 포함될 수 있습니다. 내 의견으로는, 마크를 받아 들인 나사 :)
Johannes Schaub-litb

3
대답은 훌륭합니다. 나는 여전히이 게시물을 잠시 동안 다시 방문하고 있습니다. 그건 그렇고 Visual Studio에 대한 경고에 관한 것입니다. 포드에 대한 "goto statement"는 언급 한 것처럼 MSVC 컴파일러에 대한 무지와 함께 제공됩니다. 그러나 switch / case 문의 경우 컴파일 오류가 발생합니다. 그것을 바탕으로 개념은 나는 몇 가지 테스트 포드 검사기을 만들었습니다 : stackoverflow.com/questions/12232766/test-for-pod-ness-in-c-c11/...
bruziuz

2
"POD가 아닌 클래스 유형의 객체 수명은 생성자가 완료되면 시작되고 소멸자가 완료되면 종료됩니다." 대신 마지막 부분은 "소멸자가 시작될 때"라고 말해야합니다.
Quokka

457

C ++ 11의 변경 사항은 무엇입니까?

집계

집계의 표준 정의는 약간 변경되었지만 여전히 거의 동일합니다.

집계는 사용자 제공 생성자 (12.1)가없고 비 정적 데이터 멤버 (9.2)에 대한 중괄호 또는 이니셜 라이저 가없고 개인용 또는 보호 된 비 정적 데이터 멤버 가없는 배열 또는 클래스 (Clause 9)입니다 ( 조항 11), 기본 등급 없음 (10 항) 및 가상 기능 없음 (10.3).

좋아, 무엇이 바뀌 었습니까?

  1. 이전에는 집계에 사용자가 선언 한 생성자가 없었지만 이제는 사용자가 제공 한 생성자를 가질 수 없습니다 . 차이가 있습니까? 그렇습니다. 생성자를 선언하고 기본값을 지정할 수 있기 때문입니다 .

    struct Aggregate {
        Aggregate() = default; // asks the compiler to generate the default implementation
    };

    첫 번째 선언에서 기본값으로 지정된 생성자 (또는 특수 멤버 함수) 사용자가 제공하지 않기 때문에 여전히 집계 입니다.

  2. 이제 집계 에 비 정적 데이터 멤버에 대한 중괄호 또는 이니셜 라이저 를 사용할 수 없습니다 . 이것은 무엇을 의미 하는가? 이 새로운 표준을 사용하면 클래스에서 다음과 같이 멤버를 직접 초기화 할 수 있습니다.

    struct NotAggregate {
        int x = 5; // valid in C++11
        std::vector<int> s{1,2,3}; // also valid
    };

    이 기능을 사용하면 기본적으로 고유 한 기본 생성자를 제공하는 것과 동일하므로 클래스가 더 이상 집계되지 않습니다.

따라서 집합체는 크게 변하지 않았습니다. 새로운 기능에 맞게 여전히 동일한 기본 아이디어입니다.

POD는 어떻습니까?

POD는 많은 변화를 겪었습니다. 이 새로운 표준에서는 POD에 대한 많은 이전 규칙이 완화되었으며 표준에서 정의가 제공되는 방식이 크게 변경되었습니다.

POD의 아이디어는 기본적으로 두 가지 고유 한 속성을 캡처하는 것입니다.

  1. 정적 초기화를 지원하며
  2. C ++에서 POD를 컴파일하면 C로 컴파일 된 구조체와 동일한 메모리 레이아웃이 제공됩니다.

이로 인해 정의는 두 가지 고유 한 개념으로 나뉩니다. 사소한 클래스와 표준 레이아웃 클래스 . POD보다 유용하기 때문입니다. 이 표준은 이제 거의 더 구체적인 선호하는 용어는 POD를 사용하지 사소한표준 레이아웃 개념을.

새로운 정의는 기본적으로 POD가 사소하고 표준 레이아웃을 갖는 클래스이며,이 속성은 모든 비 정적 데이터 멤버에 대해 재귀 적으로 보유해야한다고 말합니다.

POD 구조체는 간단한 클래스와 표준 레이아웃 클래스 인 비 유니언 클래스이며 비 POD 구조체 형식, 비 POD 공용체 (또는 이러한 형식의 배열)의 비 정적 데이터 멤버가 없습니다. 마찬가지로 POD 공용체는 사소한 클래스 및 표준 레이아웃 클래스 인 공용체이며 비 -POD 구조체 형식, 비 -POD 공용체 (또는 이러한 형식의 배열)의 비 정적 데이터 멤버가 없습니다. POD 클래스는 POD 구조체 또는 POD 공용체 인 클래스입니다.

이 두 속성 각각에 대해 자세히 살펴 보겠습니다.

사소한 수업

하찮은 사소한 클래스는 정적 초기화를 지원 제 재산권 상기 언급된다. 클래스가 사소하게 복사 가능한 경우 (사소한 클래스의 수퍼 세트), 장소와 같은 memcpy것으로 표현을 복사하고 결과가 동일 할 것으로 기대할 수 있습니다.

표준은 다음과 같이 간단한 클래스를 정의합니다.

사소하게 복사 가능한 클래스는 다음과 같은 클래스입니다.

— 사소한 사본 생성자가 없습니다 (12.8).

— 사소한 이동 생성자가 없습니다 (12.8).

— 사소한 사본 할당 연산자가 없습니다 (13.5.3, 12.8).

— 사소한 이동 할당 연산자 (13.5.3, 12.8)가 없으며

— 사소한 소멸자가 있습니다 (12.4).

사소한 클래스는 사소한 기본 생성자 (12.1)를 가지고 있으며 사소하게 복사 할 수있는 클래스입니다.

[ 참고 : 특히 사소한 복사 가능 또는 사소한 클래스에는 가상 기능 또는 가상 기본 클래스가 없습니다. — 끝 참고 ]

그렇다면 사소하고 사소한 것은 무엇입니까?

클래스 X의 복사 / 이동 생성자는 사용자가 제공하지 않은 경우 사소합니다.

— 클래스 X에는 가상 기능 (10.3) 및 가상 기본 클래스 (10.1)가 없으며

— 각 직접 기본 클래스 하위 오브젝트를 복사 / 이동하도록 선택된 생성자는 사소한 것이며

— 클래스 유형 (또는 그 배열) 인 X의 각 비 정적 데이터 멤버에 대해 해당 멤버를 복사 / 이동하도록 선택된 생성자는 사소합니다.

그렇지 않으면 복사 / 이동 생성자가 중요하지 않습니다.

기본적으로 이것은 복사 또는 이동 생성자가 사용자가 제공하지 않고 클래스에 가상이 없으며 클래스의 모든 멤버와 기본 클래스에 대해 재귀 적으로 보유하는 경우 사소한 것을 의미합니다.

간단한 복사 / 이동 할당 연산자의 정의는 "constructor"라는 단어를 "assignment operator"로 바꾸는 것과 매우 유사합니다.

사소한 소멸자는 비슷한 정의를 가지고 있으며 가상이 될 수 없다는 제약이 추가되었습니다.

그리고 또 다른 유사한 규칙이 사소한 기본 생성자에도 존재하며, 클래스에 brace-or-equal-initializers 가있는 비 정적 데이터 멤버가있는 경우 기본 생성자가 사소한 것이 아니라는 점이 있습니다.

다음은 모든 것을 정리하는 몇 가지 예입니다.

// empty classes are trivial
struct Trivial1 {};

// all special members are implicit
struct Trivial2 {
    int x;
};

struct Trivial3 : Trivial2 { // base class is trivial
    Trivial3() = default; // not a user-provided ctor
    int y;
};

struct Trivial4 {
public:
    int a;
private: // no restrictions on access modifiers
    int b;
};

struct Trivial5 {
    Trivial1 a;
    Trivial2 b;
    Trivial3 c;
    Trivial4 d;
};

struct Trivial6 {
    Trivial2 a[23];
};

struct Trivial7 {
    Trivial6 c;
    void f(); // it's okay to have non-virtual functions
};

struct Trivial8 {
     int x;
     static NonTrivial1 y; // no restrictions on static members
};

struct Trivial9 {
     Trivial9() = default; // not user-provided
      // a regular constructor is okay because we still have default ctor
     Trivial9(int x) : x(x) {};
     int x;
};

struct NonTrivial1 : Trivial3 {
    virtual void f(); // virtual members make non-trivial ctors
};

struct NonTrivial2 {
    NonTrivial2() : z(42) {} // user-provided ctor
    int z;
};

struct NonTrivial3 {
    NonTrivial3(); // user-provided ctor
    int w;
};
NonTrivial3::NonTrivial3() = default; // defaulted but not on first declaration
                                      // still counts as user-provided
struct NonTrivial5 {
    virtual ~NonTrivial5(); // virtual destructors are not trivial
};

표준 레이아웃

표준 레이아웃 은 두 번째 속성입니다. 표준은 이것들이 다른 언어와의 통신에 유용하다고 언급하며, 표준 레이아웃 클래스는 동등한 C 구조체 또는 공용체의 동일한 메모리 레이아웃을 갖기 때문입니다.

멤버와 모든 기본 클래스에 대해 재귀 적으로 보유해야하는 또 다른 속성입니다. 그리고 평소와 같이 가상 함수 나 가상 기본 클래스는 허용되지 않습니다. 그러면 레이아웃이 C와 호환되지 않습니다.

여기서 완화 된 규칙은 표준 레이아웃 클래스에는 동일한 액세스 제어를 가진 모든 비 정적 데이터 멤버가 있어야한다는 것입니다. 이전에는 모두 공개 되어야 했지만 이제는 모두 비공개이거나 모두 보호되는 한 비공개 또는 보호 할 수 있습니다.

상속을 사용할 때 하나만 하는 경우 전체 상속 트리에서 클래스 정적이 아닌 데이터 멤버가있을 수 있으며 첫 번째 정적이 아닌 데이터 멤버는 기본 클래스 유형일 수 없습니다 (별칭 규칙을 위반할 수 있음). 그렇지 않으면 표준이 아닙니다. 레이아웃 클래스.

다음은 표준 텍스트에서 정의가 진행되는 방식입니다.

표준 레이아웃 클래스는 다음과 같은 클래스입니다.

— 비표준 레이아웃 클래스 유형 (또는 이러한 유형의 배열) 또는 참조의 비 정적 데이터 멤버가 없습니다.

— 가상 기능 (10.3) 및 가상 기본 클래스 (10.1)가 없습니다.

— 모든 비 정적 데이터 멤버에 대해 동일한 액세스 제어 (Clause 11)가 있습니다.

— 비표준 레이아웃 기본 클래스가 없습니다.

— 가장 파생 된 클래스에 비 정적 데이터 멤버가없고 비 정적 데이터 멤버가있는 기본 클래스가 하나 이상이거나 비 정적 데이터 멤버가있는 기본 클래스가없고

— 첫 번째 비 정적 데이터 멤버와 동일한 유형의 기본 클래스가 없습니다.

표준 레이아웃 구조체는 클래스 키 구조체 또는 클래스 키 클래스로 정의 된 표준 레이아웃 클래스입니다.

표준 레이아웃 공용체는 클래스 키 공용체로 정의 된 표준 레이아웃 클래스입니다.

[ 참고 : 표준 레이아웃 클래스는 다른 프로그래밍 언어로 작성된 코드와 통신하는 데 유용합니다. 그들의 레이아웃은 9.2에 명시되어 있습니다. — 끝 참고 ]

그리고 몇 가지 예를 봅시다.

// empty classes have standard-layout
struct StandardLayout1 {};

struct StandardLayout2 {
    int x;
};

struct StandardLayout3 {
private: // both are private, so it's ok
    int x;
    int y;
};

struct StandardLayout4 : StandardLayout1 {
    int x;
    int y;

    void f(); // perfectly fine to have non-virtual functions
};

struct StandardLayout5 : StandardLayout1 {
    int x;
    StandardLayout1 y; // can have members of base type if they're not the first
};

struct StandardLayout6 : StandardLayout1, StandardLayout5 {
    // can use multiple inheritance as long only
    // one class in the hierarchy has non-static data members
};

struct StandardLayout7 {
    int x;
    int y;
    StandardLayout7(int x, int y) : x(x), y(y) {} // user-provided ctors are ok
};

struct StandardLayout8 {
public:
    StandardLayout8(int x) : x(x) {} // user-provided ctors are ok
// ok to have non-static data members and other members with different access
private:
    int x;
};

struct StandardLayout9 {
    int x;
    static NonStandardLayout1 y; // no restrictions on static members
};

struct NonStandardLayout1 {
    virtual f(); // cannot have virtual functions
};

struct NonStandardLayout2 {
    NonStandardLayout1 X; // has non-standard-layout member
};

struct NonStandardLayout3 : StandardLayout1 {
    StandardLayout1 x; // first member cannot be of the same type as base
};

struct NonStandardLayout4 : StandardLayout3 {
    int z; // more than one class has non-static data members
};

struct NonStandardLayout5 : NonStandardLayout3 {}; // has a non-standard-layout base class

결론

이 새로운 규칙을 통해 훨씬 더 많은 유형이 POD가 될 수 있습니다. 유형이 POD가 아니더라도 일부 POD 속성을 개별적으로 활용할 수 있습니다 (사소하거나 표준 레이아웃 중 하나 일 경우).

표준 라이브러리에는 헤더에서 이러한 속성을 테스트하는 특성이 있습니다 <type_traits>.

template <typename T>
struct std::is_pod;
template <typename T>
struct std::is_trivial;
template <typename T>
struct std::is_trivially_copyable;
template <typename T>
struct std::is_standard_layout;

2
다음 규칙을 자세히 설명 할 수 있습니까? a) 표준 레이아웃 클래스에는 동일한 액세스 제어를 가진 모든 비 정적 데이터 멤버가 있어야합니다. b) 전체 상속 트리에서 하나의 클래스 만 정적이 아닌 데이터 멤버를 가질 수 있으며 첫 번째 정적이 아닌 데이터 멤버는 기본 클래스 유형이 될 수 없습니다 (이는 앨리어싱 규칙을 위반할 수 있음). 특히 그 이유는 무엇입니까? 이후의 규칙에 대해 앨리어싱 해제의 예를 제공 할 수 있습니까?
Andriy Tylychko

@AndyT : 내 대답을 참조하십시오. 나는 최선을 다해 대답하려고 노력했다.
Nicol Bolas

5
C ++ 14에 대해이를 업데이트하여 집계에 대한 "중괄호 또는 이니셜 라이저 없음"요구 사항을 제거 할 수 있습니다.
TC

@TC 감사합니다. 변경 사항을 곧 찾아보고 업데이트하겠습니다.
R. Martinho Fernandes

1
앨리어싱 관련 : 클래스 C에 (빈) 기본 X가 있고 C의 첫 번째 데이터 멤버가 X 인 경우 해당 첫 번째 멤버는 기본 X와 동일한 오프셋에있을 수없는 C ++ 레이아웃 규칙이 있습니다. 그것을 피하기 위해 필요한 경우 더미 패딩 바이트를 미리 얻습니다. 동일한 주소에 X (또는 서브 클래스)의 두 인스턴스가 있으면 주소를 통해 다른 인스턴스를 구별해야하는 항목이 손상 될 수 있습니다 (빈 인스턴스는 구별 할 수있는 것이 없습니다 ...). 어쨌든 해당 패딩 바이트를 넣어야 할 경우 '레이아웃 호환'이 중단됩니다.
greggo

106

C ++ 14에서 변경된 사항

Draft C ++ 14 표준을 참조 할 수 있습니다 을 참조 할 수 있습니다.

집계

이에 대해서는 다음과 같은 정의를 제공하는 8.5.1 집계 섹션에서 다룹니다 .

집계는 사용자 제공 생성자 (12.1), 개인 또는 보호 된 비 정적 데이터 멤버 (Clause 11), 기본 클래스 (Clause 10) 및 가상 함수 (10.3)가없는 배열 또는 클래스 (Clause 9)입니다. ).

유일한 변경 사항은 이제 클래스 내 멤버 이니셜 라이저를 추가 해도 클래스가 집계되지 않습니다. 따라서 C ++ 11 의 다음 예제는 멤버 인 이니셜 라이저가있는 클래스에 대한 집계 초기화입니다 .

struct A
{
  int a = 3;
  int b = 3;
};

C ++ 11에서는 집계가 아니지만 C ++ 14에 있습니다. 이 변경 사항은 N3605 : 멤버 이니셜 라이저 및 집계 에서 다루며 다음과 같은 요약을 갖습니다.

Bjarne Stroustrup과 Richard Smith는 집계 초기화와 멤버 이니셜 라이저가 함께 작동하지 않는 문제를 제기했습니다. 이 백서에서는 Smith가 제안한 단어를 채택하여 집계에서 멤버 초기화 도구를 사용할 수없는 제한 사항을 제거함으로써 문제를 해결하도록 제안합니다.

포드는 그대로 유지

POD ( plain old data ) 구조체에 대한 정의는 9 클래스 섹션에서 다루고 있습니다 .

POD 구조체 ( 110) 는 사소한 클래스 및 표준 레이아웃 클래스 인 비 유니언 클래스이며 비 -POD 구조체 타입, 비 -POD 유니언 (또는 이러한 타입의 어레이)의 비-정적 데이터 멤버를 갖지 않는다. 마찬가지로 POD 공용체는 사소한 클래스 및 표준 레이아웃 클래스 인 공용체이며 비 -POD 구조체 형식, 비 -POD 공용체 (또는 이러한 형식의 배열)의 비 정적 데이터 멤버가 없습니다. POD 클래스는 POD 구조체 또는 POD 공용체 인 클래스입니다.

이것은 C ++ 11과 같은 표현입니다.

C ++ 14의 표준 레이아웃 변경

의견에서 언급했듯이 포드표준 레이아웃 의 정의에 의존 하며 C ++ 14의 변경 사항은 사실이지만 C ++ 14에 적용된 결함 보고서를 통해 이루어졌습니다.

3 명의 DR이있었습니다 :

따라서 표준 레이아웃 은이 Pre C ++ 14에서 나왔습니다.

표준 레이아웃 클래스는 다음과 같은 클래스입니다.

  • (7.1) 비표준 레이아웃 클래스 (또는 이러한 유형의 배열) 유형의 비 정적 데이터 멤버 또는 참조가 없습니다.
  • (7.2)에는 가상 함수 ([class.virtual])와 가상 기본 클래스 ([class.mi])가 없습니다.
  • (7.3) 모든 비 정적 데이터 멤버에 대해 동일한 액세스 제어 (Clause [class.access])를 갖습니다.
  • (7.4)에는 비표준 레이아웃 기본 클래스가 없습니다.
  • (7.5) 가장 파생 된 클래스에는 비 정적 데이터 멤버가없고 비 정적 데이터 멤버가있는 기본 클래스가 하나만 있거나 비 정적 데이터 멤버가있는 기본 클래스가없고
  • (7.6)에는 첫 번째 비 정적 데이터 멤버와 동일한 유형의 기본 클래스가 없습니다 .109

C ++ 14이 :

클래스 S는 다음과 같은 경우 표준 레이아웃 클래스입니다.

  • (3.1) 비표준 레이아웃 클래스 (또는 이러한 유형의 배열) 유형의 비 정적 데이터 멤버 또는 참조가 없습니다.
  • (3.2) 가상 기능과 가상 기본 클래스가 없으며,
  • (3.3) 모든 비 정적 데이터 멤버에 대해 동일한 액세스 제어를 갖습니다.
  • (3.4) 비표준 레이아웃 기본 클래스가 없으며
  • (3.5) 주어진 유형의 기본 클래스 하위 객체를 하나 이상
  • (3.6) 클래스와 그 기본 클래스의 모든 비 정적 데이터 멤버와 비트 필드가 동일한 클래스에서 처음 선언되었으며,
  • (3.7)은 기본 클래스로서 유형의 세트 M (S)의 요소를 갖지 않으며, 임의의 유형 X에 대해 M (X)는 다음과 같이 정의된다. 104 [주 : M (X)는 X에서 0 오프셋에있을 수있는 모든 비 기본 클래스 하위 객체 — 종료 참고]
    • (3.7.1) X가 비 정적 데이터 멤버가없는 (비상 속적으로 상속 된) 비 유니언 클래스 유형 인 경우, 세트 M (X)는 비어 있습니다.
    • (3.7.2) X가 크기가 0 인 X0의 비 정적 데이터 멤버 또는 X의 첫 번째 비 정적 데이터 멤버 인 비 유니언 클래스 유형 인 경우 (이 멤버는 익명 공용체 일 수 있음) )에서 세트 M (X)는 X0과 M (X0)의 요소로 구성됩니다.
    • (3.7.3) X가 공용체 유형 인 경우, 집합 M (X)은 모든 M (Ui)과 모든 Ui를 포함하는 집합이며, 각 Ui는 X의 비 정적 데이터 멤버의 형식입니다. .
    • (3.7.4) X가 요소 유형이 Xe 인 배열 유형 인 경우 세트 M (X)는 Xe와 M (Xe)의 요소로 구성됩니다.
    • (3.7.5) X가 클래스가 아닌 비 배열 유형 인 경우 집합 M (X)는 비어 있습니다.

4
기본적으로 구성 가능한 한 집계가 기본 클래스를 갖도록 허용하는 제안이 있습니다. N4404
Shafik Yaghmour

POD는 동일하게 유지 될 수 있지만 POD의 요구 사항 인 C ++ 14 StandardLayoutType은 cppref에 따라 변경되었습니다. en.cppreference.com/w/cpp/named_req/StandardLayoutType
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 고마워요, 내가 어떻게 그리웠는지 모르겠어요. 앞으로 며칠 동안 업데이트를 시도 할 것입니다.
Shafik Yaghmour

C ++ 14에서는 POD이지만 C ++ 11에서는 그렇지 않은 예제를 생각해 낼 수 있는지 알려주세요. :-) 자세한 예제 목록을 시작했습니다 : stackoverflow.com/questions/146452/what- – 포드-타입 -c /…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 그래서 여기서 일어난 것은 C ++ 11C ++ 14 의 표준 레이아웃 설명을 보면 일치하는 것입니다. 결함을 통해 적용된 변경 사항은 C ++ 14로 다시보고됩니다. 그래서 내가 원래 이것을 썼을 때 그것은
맞았다 : -p

47

다음 규칙을 자세히 설명해 주시겠습니까?

나는 시도 할 것이다 :

a) 표준 레이아웃 클래스에는 동일한 액세스 제어를 가진 모든 비 정적 데이터 멤버가 있어야합니다.

그건 간단합니다 : 모든 비 정적 데이터 멤버가 있어야 모든public, private또는 protected. 당신은 일부 public와 일부를 가질 수 없습니다 private.

그것들에 대한 추론은 "표준 레이아웃"과 "표준 레이아웃이 아님"을 전혀 구분하지 않는 추론에 따릅니다. 즉, 컴파일러에게 물건을 메모리에 넣는 방법을 자유롭게 선택할 수 있도록합니다. vtable 포인터에 관한 것이 아닙니다.

98 년에 C ++을 표준화했을 때 사람들은 기본적으로 사람들이 C ++를 어떻게 구현할 것인지 예측해야했습니다. 그들은 다양한 C ++의 풍미에 대해 약간의 구현 경험을 가지고 있었지만 일에 대해서는 확신하지 못했습니다. 그래서 그들은 신중한 결정을 내 렸습니다. 컴파일러에게 가능한 많은 자유를주십시오.

이것이 C ++ 98에서 POD의 정의가 매우 엄격한 이유입니다. C ++ 컴파일러는 대부분의 클래스에서 멤버 레이아웃에 대해 위도를 부여했습니다. 기본적으로 POD 유형은 특별한 경우를 위해 고안된 것입니다.

C ++ 11을 작업 할 때는 컴파일러에 대한 경험이 훨씬 많았습니다. 그리고 그들은 깨달았습니다 ... C ++ 컴파일러 작성자는 정말 게으르다. 그들은이 모든 자유를했다,하지만 그들은하지 않았다 그것으로 아무것도.

표준 레이아웃의 규칙은 일반적인 관행을 다소 체계화합니다. 대부분의 컴파일러는 구현하기 위해 (해당 유형 특성에 대한 몇 가지 사항을 제외하고) 전혀 변경하지 않아도됩니다.

이제 public/에 관해서는 private상황이 다릅니다. 어떤 멤버 public대 vs. 순서를 바꿀 자유private 실제로 특히 디버깅 빌드에서 컴파일러에게 중요 할 수 있습니다. 표준 레이아웃의 요점은 다른 언어와의 호환성이 있다는 것이므로 디버그와 릴리스에서 레이아웃을 다르게 할 수는 없습니다.

그러면 실제로 사용자를 해치지 않는다는 사실이 있습니다. 캡슐화 된 클래스를 만드는 경우 모든 데이터 멤버가 private어쨌든 될 가능성이 높습니다. 일반적으로 공개 데이터 멤버를 완전히 캡슐화 된 유형에 노출시키지 않습니다. 따라서 이는 해당 부서를 원하는 소수의 사용자에게만 문제가됩니다.

따라서 큰 손실은 없습니다.

b) 전체 상속 트리에서 오직 하나의 클래스 만이 비 정적 데이터 멤버를 가질 수있다.

이것의 이유는 그들이 표준 레이아웃을 다시 표준화 한 이유, 즉 일반적인 관행으로 되돌아옵니다.

실제로 물건을 저장하는 상속 트리의 두 멤버를 갖는 경우 에는 일반적인 관행 이 없습니다 . 일부는 파생 클래스보다 기본 클래스를 배치하고 다른 클래스는 다른 방식으로 수행합니다. 두 개의 기본 클래스에서 온 멤버를 어떤 방법으로 주문합니까? 등등. 컴파일러는 이러한 질문에 대해 크게 의견을 나누고 있습니다.

또한 0 / 1 / 무한 규칙으로 인해 멤버와 함께 두 개의 클래스를 가질 수 있다고 말하면 원하는만큼 말할 수 있습니다. 이를 처리하는 방법에 대한 많은 레이아웃 규칙을 추가해야합니다. 다중 상속이 어떻게 작동하는지, 어떤 클래스가 다른 클래스보다 먼저 데이터를 넣는 등의 방법을 말해야합니다. 이는 물질적 이득이 거의없는 규칙입니다.

가상 함수 및 기본 생성자 표준 레이아웃이없는 모든 것을 만들 수는 없습니다.

정적이 아닌 첫 번째 데이터 멤버는 기본 클래스 유형일 수 없습니다 (이는 앨리어싱 규칙을 위반할 수 있음).

나는 이것에 대해 실제로 말할 수 없다. C ++의 앨리어싱 규칙을 실제로 이해하기에 충분한 교육을받지 못했습니다. 그러나 기본 멤버가 기본 클래스 자체와 동일한 주소를 공유한다는 사실과 관련이 있습니다. 그건:

struct Base {};
struct Derived : Base { Base b; };

Derived d;
static_cast<Base*>(&d) == &d.b;

그리고 이것은 아마도 C ++의 앨리어싱 규칙에 위배됩니다. 어떤 식 으로든

그러나 이것을 고려하십시오 : 실제로 이것을 수행하는 능력이 얼마나 유용 할 수 있습니까? 비 정적 데이터 멤버는 하나의 클래스 만 가질 수 있으므로 해당 멤버 Derived여야합니다 ( Base멤버로서 있으므로). 따라서 데이터가 비어 Base 있어야합니다 . 그리고 기본 클래스 뿐만 아니라Base 비어있는 경우 왜 데이터 멤버가 있습니까?

Base비어 있기 때문에 상태가 없습니다. 따라서 정적이 아닌 멤버 함수는 this포인터가 아닌 매개 변수를 기반으로 수행하는 작업을 수행 합니다.

다시 한번 : 큰 손실이 없습니다.


설명 주셔서 감사합니다, 그것은 많은 도움이됩니다. 아마도 유형 에도 불구 static_cast<Base*>(&d)하고 &d.b같은 Base*유형 일지라도 , 서로 다른 점을 가리 키므로 앨리어싱 규칙을 어기 게됩니다. 수정 해주세요.
Andriy Tylychko

1
왜 정적 클래스가 아닌 데이터 멤버를 하나의 클래스 만 가질 수 Derived있다면 그 클래스 여야합니까?
Andriy Tylychko

3
@AndyT : Derived첫 번째 멤버가 기본 클래스가 되려면 기본 클래스와 멤버 라는 두 가지가 있어야합니다 . 또한 계층 구조에서 하나의 클래스 만 멤버를 가질 수 있으며 여전히 표준 레이아웃 일 수 있으므로 기본 클래스는 멤버를 가질 수 없습니다.
Nicol Bolas

3
@AndyT, 예, 앨리어싱 규칙에 대해 본질적으로 IME입니다. 고유 한 메모리 주소를 가지려면 동일한 유형의 두 개의 개별 인스턴스가 필요합니다. 이렇게하면 메모리 주소를 사용하여 객체 ID를 추적 할 수 있습니다. 기본 객체와 첫 번째 파생 멤버는 서로 다른 인스턴스 여야하므로 패딩이 추가되어 클래스의 레이아웃에 영향을 미칩니다. 그것들이 다른 타입이라면, 그것은 중요하지 않을 것입니다; 유형이 다른 객체는 동일한 주소 (예 : 클래스 및 첫 번째 데이터 멤버)를 가질 수 있습니다.
Adam H. Peterson

46

C ++ 17의 변화

여기 에서 C ++ 17 국제 표준 최종 초안을 다운로드 하십시오 .

집계

C ++ 17은 집계 및 집계 초기화를 확장하고 향상시킵니다. 표준 라이브러리에는 이제 std::is_aggregate유형 특성 클래스 도 포함됩니다 . 섹션 11.6.1.1 및 11.6.1.2의 공식 정의는 다음과 같습니다 (내부 참조 제거).

집계는
사용자 제공, 명시 적 또는 상속 된 생성자,
개인 또는 보호 된 비 정적 데이터 멤버,
가상 함수 및
가상, 개인 또는 보호 된 기본 클래스 가없는 배열 또는 클래스입니다.
[참고 : 집계 초기화는 보호 및 개인 기본 클래스의 멤버 또는 생성자에 대한 액세스를 허용하지 않습니다. — 끝 주]
집합의 요소는 다음과 같습니다.
— 배열, 아래 첨자 순서가 증가하는 배열 요소 또는
— 선언 순서의 직접 기본 클래스 다음에 정적이 아닌 직접 데이터 멤버가 오는 클래스의 경우 익명의 조합원을 선언 순서대로

무엇이 바뀌 었습니까?

  1. 집계는 이제 비가 상 공개 클래스를 공개 할 수 있습니다. 또한 기본 클래스를 집계 할 필요는 없습니다. 이들이 집계되지 않으면 목록 초기화됩니다.
struct B1 // not a aggregate
{
    int i1;
    B1(int a) : i1(a) { }
};
struct B2
{
    int i2;
    B2() = default;
};
struct M // not an aggregate
{
    int m;
    M(int a) : m(a) { }
};
struct C : B1, B2
{
    int j;
    M m;
    C() = default;
};
C c { { 1 }, { 2 }, 3, { 4 } };
cout
    << "is C aggregate?: " << (std::is_aggregate<C>::value ? 'Y' : 'N')
    << " i1: " << c.i1 << " i2: " << c.i2
    << " j: " << c.j << " m.m: " << c.m.m << endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  1. 명시 적 기본 생성자는 허용되지 않습니다.
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. 상속 생성자는 허용되지 않습니다
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


사소한 클래스

사소한 클래스의 정의는 C ++ 14에서 해결되지 않은 몇 가지 결함을 해결하기 위해 C ++ 17에서 재 작업되었습니다. 변경 사항은 본질적으로 기술적이었습니다. 12.0.6의 새로운 정의는 다음과 같습니다 (내부 참조 제거).

사소하게 복사 가능한 클래스는 다음과 같은 클래스입니다.
— 각 복사 생성자, 이동 생성자, 복사 할당 연산자 및 이동 할당 연산자가 삭제되거나 사소한 경우
— 삭제되지 않은 복사 생성자, 이동 생성자, 복사 할당 연산자, 또는 이동 할당 연산자 및
— 삭제되지 않은 간단한 소멸자가 있습니다.
사소한 클래스는 사소하게 복사 할 수 있고 하나 이상의 기본 생성자를 갖는 클래스입니다.이 클래스는 모두 사소하거나 삭제되고 적어도 하나는 삭제되지 않습니다. [참고 : 특히, 사소한 복사 가능 또는 사소한 클래스에는 가상 기능 또는 가상 기본 클래스가 없습니다 .— 끝 참고]

변경 사항 :

  1. C ++ 14에서 클래스가 사소한 경우 클래스는 사소하지 않은 복사 / 이동 생성자 / 할당 연산자를 가질 수 없었습니다. 그러나 기본 생성자 / 연산자로 암시 적으로 선언 된 것은 사소하지는 않지만 삭제 것으로 정의 될 수 있습니다. 예를 들어 클래스에 복사 / 이동할 수없는 클래스 유형의 하위 객체가 포함되어 있기 때문입니다. 그러한 사소한, 삭제 된 것으로 정의 된 생성자 / 연산자가 있으면 전체 클래스가 사소하지 않게됩니다. 소멸자와 비슷한 문제가 발생했습니다. C ++ 17은 그러한 생성자 / 연산자의 존재로 인해 클래스가 사소하게 복사 가능하지 않으므로 사소하지 않으며 사소하게 복사 가능한 클래스에는 사소하고 삭제되지 않은 소멸자가 있어야 함을 명시합니다. DR1734 , DR1928
  2. C ++ 14는 사소하게 복사 가능한 클래스, 즉 사소한 클래스가 모든 복사 / 이동 생성자 / 할당 연산자를 삭제 된 것으로 선언하도록 허용했습니다. 그러나 class와 같은 클래스도 표준 레이아웃 인 경우을 사용하여 합법적으로 복사 / 이동할 수 있습니다 std::memcpy. 삭제 된 모든 생성자 / 할당 연산자를 정의하여 클래스 작성자가 클래스를 복사 / 이동할 수 없도록 의도했지만 클래스가 여전히 사소하게 복사 가능한 클래스의 정의를 충족했기 때문에 이는 의미 상 모순입니다. 따라서 C ++ 17에는 사소하게 복사 가능한 클래스에 최소한 하나의 사소하고 삭제되지 않은 (공개적으로 액세스 할 수는 없지만) 복사 / 이동 생성자 / 할당 연산자가 있어야한다고 명시하는 새로운 절이 있습니다. N4148 참조 , DR1734
  3. 세 번째 기술 변경은 기본 생성자와 비슷한 문제에 관한 것입니다. C ++ 14에서 클래스는 암시 적으로 삭제 된 것으로 정의 된 사소한 기본 생성자를 가질 수 있지만 여전히 사소한 클래스입니다. 새로운 정의는 사소한 클래스에 최소한 하나의 사소한 삭제되지 않은 기본 생성자가 있어야 함을 명시합니다. DR1496 참조

표준 레이아웃 클래스

결함 보고서를 해결하기 위해 표준 레이아웃의 정의도 수정되었습니다. 다시 한 번 변경 사항은 기술적 인 부분이었습니다. 다음은 표준 (12.0.7)의 텍스트입니다. 이전과 같이 내부 참조는 생략됩니다.

클래스 S는 다음과 같은 경우 표준 레이아웃 클래스입니다.
— 비표준 레이아웃 클래스 (또는 이러한 유형의 배열) 유형의 비 정적 데이터 멤버
가 없거나 — 가상 기능이없고 가상 기본 클래스가없는 경우
— 모든 비 정적 데이터 멤버에 대해 동일한 액세스 제어가 있으며,
비표준 레이아웃 기본 클래스가 없으며,
특정 유형의 기본 클래스 하위 오브젝트가 하나만
있습니다. 모든 비 정적 데이터 멤버 및 비트 필드가 있습니다. 클래스와 그 기본 클래스는 같은 클래스에서 처음 선언되었으며
— 기본 클래스 로 유형 M (S) 세트 (아래 정의)의 요소가 없습니다 .108
M (X)는 다음과 같이 정의됩니다.
-X가 비 유니언 클래스 유형 인 경우 ( 비 정적 데이터 멤버 일 수 있으며, 세트 M (X)이 비어 있습니다.
— X가 비 정적 데이터 멤버의 첫 번째 비 정적 데이터 멤버의 유형이 X0 (이 멤버는 익명 공용체 일 수 있음) 인 경우, 집합 M (X)는 X0과 M (X0)의 요소로 구성됩니다.
— X가 공용체 유형 인 경우, 집합 M (X)는 모든 M (Ui)과 모든 Ui를 포함하는 집합의 합집합입니다. 여기서 각 Ui는 X의 정적 비 정적 데이터 멤버의 유형입니다. — X가 클래스가 아닌 비 배열 유형 인 경우 세트 M (X)가 비어 있습니다.
— X가 요소 유형이 Xe 인 배열 유형 인 경우 세트 M (X)는 Xe와 M (Xe)의 요소로 구성됩니다. [참고 : M (X)는 표준 레이아웃 클래스에서 X에서 0 오프셋이되도록 보장 된 모든 비 기본 클래스 하위 객체의 유형 집합입니다. —end note] [예 :



struct B { int i; }; // standard-layout class
struct C : B { }; // standard-layout class
struct D : C { }; // standard-layout class
struct E : D { char : 4; }; // not a standard-layout class
struct Q {};
struct S : Q { };
struct T : Q { };
struct U : S, T { }; // not a standard-layout class
—end example]
108) 이렇게하면 클래스 유형이 동일하고 가장 파생 된 동일한 오브젝트에 속하는 두 개의 서브 오브젝트가 동일한 주소에 할당되지 않습니다.

변경 사항 :

  1. 파생 트리에서 하나의 클래스에만 비 정적 데이터 멤버가 있어야한다는 요구 사항이 상속 될 수있는 클래스가 아니라 해당 데이터 멤버가 처음 선언 된 클래스를 참조하고이 요구 사항을 비 정적 비트 필드로 확장 함 . 또한 표준 레이아웃 클래스에는 "어떤 주어진 유형의 기본 클래스 하위 객체가 최대 하나 있습니다"라고 설명했습니다. DR1813 , DR1881 참조
  2. 표준 레이아웃의 정의는 기본 클래스의 유형이 첫 번째 비 정적 데이터 멤버와 동일한 유형이되도록 허용하지 않았습니다. 오프셋 0의 데이터 멤버가 기본 클래스와 동일한 유형을 갖는 상황을 피하기위한 것입니다. C ++ 17 표준은 이러한 유형을 금지하기 위해 "표준 레이아웃 클래스에서 제로 오프셋 (offset)으로 보장되는 모든 비 기본 클래스 하위 오브젝트 유형 세트"에 대한보다 엄격하고 재귀적인 정의를 제공합니다. 기본 클래스의 유형이 아닙니다. DR1672 , DR2120을 참조하십시오 .

참고 : C ++ 표준위원회는 공개 된 C ++ 14 표준에 없지만 새 언어는 C ++ 14에 적용하기 위해 결함 보고서를 기반으로 한 위의 변경 사항을 의도했습니다. C ++ 17 표준입니다.


참고 방금 표준 레이아웃 변경 결함에 CD4 상태 가 있음을 내 대답을 업데이트했습니다. 즉, 실제로 C ++ 14에 적용됩니다. 그렇기 때문에 내 답변에 b / c가 포함되지 않았습니다.
Shafik Yaghmour

이 질문에 대해 현상금을 시작했습니다.
Shafik Yaghmour

@ShafikYaghmour에게 감사합니다. 결함 보고서 상태를 검토하고 이에 따라 답변을 수정합니다.
ThomasMcLeod

@ShafikYaghmour, C ++ 14 프로세스를 검토 한 결과 2014 년 6 월에 이러한 DR이 "수용"되었지만 Rapperswil은 2014 년 2 월의 언어를 만나는 Issaquah 미팅이 C ++ 14가 된 것으로 나타났습니다. 참조 isocpp.org/blog/2014/07/trip-report-summer-iso-c-meeting "ISO는 우리가 공식적으로 C ++ 종이 작업에 어떤 편집을 승인하지 않았다 규칙에 따라." 뭔가 빠졌습니까?
ThomasMcLeod

'CD4'상태이므로 C ++ 14 모드에서 적용해야합니다.
Shafik Yaghmour

14

어떤 변화

이 질문의 나머지 분명한 주제에 따라 골재의 의미와 사용은 모든 표준에 따라 계속 변경됩니다. 지평선에는 몇 가지 중요한 변화가 있습니다.

사용자 선언 생성자가있는 유형 P1008

C ++ 17에서이 유형은 여전히 ​​집계입니다.

struct X {
    X() = delete;
};

따라서 X{}생성자 호출이 아닌 집계 초기화이기 때문에 여전히 컴파일됩니다. 참고 : 개인 생성자는 언제 개인 생성자가 아닌가?

C ++ 20에서는 다음과 같은 제한이 필요합니다.

사용자 제공 explicit, 또는 상속 된 생성자 없음

사용자 선언 또는 상속 된 생성자 없음

이것은 C ++ 20 작업 초안 에 채택되었습니다 . 어느 쪽도 아니 X여기에 나 C링크 된 문제는 20 ++ C에서 집계되지 않습니다.

이것은 또한 다음 예제에서 요요 효과를 만듭니다.

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};

C ++ 11/14에서는 기본 클래스로 인해 집계 B아니므 로 액세스 가능한 지점 에서을 B{}호출 B::B()하는 값 초기화를 수행합니다 A::A(). 이것은 잘 구성되었습니다.

C ++ 17에서는 B기본 클래스가 허용되어 B{}집계가 초기화되어 집계가 초기화되었습니다. 이를 위해서는 copy-list-initializing Afrom 이 필요 {}하지만 컨텍스트 외부에서는 B액세스 할 수 없습니다. C ++ 17에서는 이것이 잘못되었습니다 ( auto x = B();그렇지만 괜찮을 것입니다).

C ++ 20에서는 위의 규칙 변경으로 B인해 기본 클래스가 아니라 사용자가 선언 한 기본 생성자 때문에 기본적으로 집계가 중단됩니다. 다시 우리는 B의 생성자를 살펴 보았습니다.이 스 니펫은 제대로 구성되었습니다.

괄호로 묶은 값 목록에서 집계 초기화 P960

일반적인 문제는 emplace()집계와 함께 스타일 생성자 를 사용하는 것입니다 .

struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error

때문에,하지 작업을 수행 emplace효과적으로 초기화 수행하려고합니다 X(1, 2)유효하지 않습니다. 일반적인 해결책은에 생성자를 추가하는 X것이지만이 제안 (현재 코어를 통해 작동)을 통해 집계는 올바른 일을하는 생성자를 효과적으로 생성하고 일반 생성자처럼 동작합니다. 위의 코드는 C ++ 20에서 그대로 컴파일됩니다.

집계 P1021 (구체적으로 P1816 )에 대한 클래스 템플릿 인수 공제 ( CTAD )

C ++ 17에서는 컴파일되지 않습니다.

template <typename T>
struct Point {
    T x, y;
};

Point p{1, 2}; // error

사용자는 모든 집계 템플릿에 대한 자체 추론 가이드를 작성해야합니다.

template <typename T> Point(T, T) -> Point<T>;

그러나 이것은 어떤 의미에서 "명백한 일"이며 기본적으로 보일러 플레이트이므로 언어가이를 대신해 줄 것입니다. 이 예제는 C ++ 20으로 컴파일됩니다 (사용자가 제공 한 추론 가이드가 필요 없음).


나는 공감할 것이지만, 이것을 추가하는 것이 조금 일찍 느껴지지만 C ++ 2x가 완료되기 전에이를 바꿀 주요 내용이 무엇인지 모른다.
Shafik Yaghmour

@ShafikYaghmour 그래, 아마 너무 일찍. 그러나 SD가 새로운 언어 기능의 마감일이라는 것을 감안할 때 이것들은 내가 알고있는 유일한 두 기내입니다-최악의 경우 나중에이 섹션 중 하나를 차단 삭제합니까? 나는 그 현상금과 관련된 질문을 보았고 잊기 전에 차임하기에 좋은 시간이라고 생각했습니다.
Barry

나는 비슷한 경우에 두 번 유혹을 받았다는 것을 이해합니다. 나는 항상 중요한 일이 바뀔 것이라고 걱정하고 그것을 다시 써야 할 것입니다.
Shafik Yaghmour

@ShafikYaghmour 아무것도 바뀌지 않을 것 같습니다 :)
Barry

C ++ 20이 이미 릴리스 된 상태에서 이것이 업데이트되기를 바랍니다
Noone AtAll
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.