기본 생성자와 소멸자에서 “= default”는 “{}”과 어떻게 다릅니 까? 동일한 효과를 얻을 수있는 것

나는 원래 이것을 소멸자에 대한 질문으로 만 게시했지만 이제 기본 생성자를 고려하고 있습니다. 원래 질문은 다음과 같습니다.

클래스에 가상의 소멸자를 제공하고 싶지만 컴파일러가 생성하는 것과 동일한 소멸자를 제공하려면 =default다음을 사용할 수 있습니다 .

class Widget {
public:
   virtual ~Widget() = default;
};

그러나 빈 정의를 사용하여 타이핑을 줄이면 동일한 효과를 얻을 수있는 것 같습니다.

class Widget {
public:
   virtual ~Widget() {}
};

이 두 정의가 다르게 행동하는 방법이 있습니까?

이 질문에 대한 답변을 기반으로 기본 생성자의 상황이 비슷해 보입니다. 소멸자에 대해 ” =default“와 ” {}“의 의미에 거의 차이가 없다는 것을 감안할 때 기본 생성자에 대한 이러한 옵션 사이에 의미에 거의 차이가 없습니까? 즉, 해당 유형의 객체가 생성되고 파괴되는 유형을 생성한다고 가정하면 왜 말하고 싶습니까?

Widget() = default;

대신에

Widget() {}

?

원래 게시물이 일부 SO 규칙을 위반 한 후이 질문을 확장하면 죄송합니다. 기본 생성자에 대해 거의 동일한 질문을 게시하면 바람직하지 않은 옵션으로 생각났습니다.



답변

이것은 소멸자와 생성자에 대해 요청할 때 완전히 다른 질문입니다.

만약 당신의 소멸자가 그렇다면 하워드가 지적한 것처럼virtual 그 차이는 무시할 만하다 . 그러나 소멸자가 가상아닌 경우 완전히 다른 이야기입니다. 생성자도 마찬가지입니다.

= default특수 멤버 함수 (기본 생성자, 복사 / 이동 생성자 / 할당, 소멸자 등)에 구문을 사용 한다는 것은 단순히하는 것과 매우 다른 의미 {}입니다. 후자의 경우, 기능은 “사용자 제공”이됩니다. 그리고 그것은 모든 것을 바꿉니다.

이것은 C ++ 11의 정의에 의한 간단한 클래스입니다.

struct Trivial
{
  int foo;
};

기본 생성을 시도하면 컴파일러에서 기본 생성자를 자동으로 생성합니다. 복사 / 이동 및 파괴도 마찬가지입니다. 사용자가 이러한 멤버 함수를 제공하지 않았으므로 C ++ 11 사양에서는이를 “사소한”클래스로 간주합니다. 따라서 memcpy처럼 내용을 초기화하는 등의 작업을 수행하는 것이 합법적입니다.

이:

struct NotTrivial
{
  int foo;

  NotTrivial() {}
};

이름에서 알 수 있듯이 이것은 더 이상 사소한 것이 아닙니다. 사용자가 제공하는 기본 생성자가 있습니다. 비어 있는지는 중요하지 않습니다. C ++ 11의 규칙에 관한 한, 이것은 사소한 유형이 될 수 없습니다.

이:

struct Trivial2
{
  int foo;

  Trivial2() = default;
};

이름에서 알 수 있듯이 이것은 사소한 유형입니다. 왜? 컴파일러에게 기본 생성자를 자동으로 생성하도록 지시했기 때문입니다. 따라서 생성자는 “사용자 제공”이 아닙니다. 따라서이 유형에는 사용자가 제공 한 기본 생성자가 없으므로 유형이 간단합니다.

= default구문은 같은 기능의 생성을 방지 멤버 함수를 추가 복사 생성자 / 지정, 같은 일을 주로있다. 그러나 컴파일러에서 특수 동작을 트리거하기 때문에 기본 생성자 / 소멸자에서도 유용합니다.


답변

둘 다 사소하지 않습니다.

베이스와 멤버의 noexcept 사양에 따라 둘 다 동일한 noexcept 사양을 갖습니다.

지금까지 내가 감지하는 유일한 차이점 Widget은 액세스 할 수 없거나 삭제 된 소멸자를 가진 기본 또는 멤버가 포함되어있는 경우입니다 .

struct A
{
private:
    ~A();
};

class Widget {
    A a_;
public:
#if 1
   virtual ~Widget() = default;
#else
   virtual ~Widget() {}
#endif
};

그러면 =default솔루션이 컴파일되지만 Widget파괴 가능한 유형은 아닙니다. 즉,를 파괴하려고 Widget하면 컴파일 타임 오류가 발생합니다. 그러나 그렇지 않은 경우 작업 프로그램이 있습니다.

Otoh, 사용자가 제공 한 소멸자를 제공 하면 다음을 파괴하는지 여부가 컴파일되지 않습니다 Widget.

test.cpp:8:7: error: field of type 'A' has private destructor
    A a_;
      ^
test.cpp:4:5: note: declared private here
    ~A();
    ^
1 error generated.


답변

사이의 중요한 차이점

class B {
    public:
    B(){}
    int i;
    int j;
};

class B {
    public:
    B() = default;
    int i;
    int j;
};

로 정의 된 기본 생성자 B() = default;사용자 정의 아님으로 간주 됩니다 . 이는 다음 과 같이 값 초기화의 경우

B* pb = new B();  // use of () triggers value-initialization

생성자를 전혀 사용하지 않는 특별한 종류의 초기화가 발생하며 내장 유형의 경우 초기화0 이됩니다 . 이 경우에는 발생 B(){}하지 않습니다. C ++ 표준 n3337 § 8.5 / 7에 따르면

T 유형의 오브젝트를 값으로 초기화하는 것은 다음을 의미합니다.

— T가 사용자 제공 생성자
(12.1)를 가진 (가능하면 cv-qualified) 클래스 유형 (Clause 9 ) 인 경우, T의 기본 생성자가 호출됩니다 (T에 액세스 가능한 기본 생성자가없는 경우 초기화는 잘못 구성됩니다) );

— T가 사용자 제공 생성자 가없는 (un-cv-qualified) 비 유니언 클래스 유형
인 경우, 객체는 0으로 초기화되며, 암시 적으로 선언 된 기본 생성자가 중요하지 않은 경우 해당 생성자가 호출됩니다.

— T가 배열 유형 인 경우 각 요소는 값으로 초기화됩니다. — 그렇지 않으면 객체가 0으로 초기화됩니다.

예를 들면 다음과 같습니다.

#include <iostream>

class A {
    public:
    A(){}
    int i;
    int j;
};

class B {
    public:
    B() = default;
    int i;
    int j;
};

int main()
{
    for( int i = 0; i < 100; ++i) {
        A* pa = new A();
        B* pb = new B();
        std::cout << pa->i << "," << pa->j << std::endl;
        std::cout << pb->i << "," << pb->j << std::endl;
        delete pa;
        delete pb;
    }
  return 0;
}

가능한 결과 :

0,0
0,0
145084416,0
0,0
145084432,0
0,0
145084416,0
//...

http://ideone.com/k8mBrd


답변