기본 생성자와 소멸자에서 "= default"는 "{}"과 어떻게 다릅니 까?


169

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

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

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

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

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

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

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

Widget() = default;

대신에

Widget() {}

?

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


1
내가 아는 것은 아니지만 = default더 명백한 imo이며 생성자를 통한 지원과 일치합니다.
— chris

11
확실하지는 않지만 전자는 "사소한 소멸자"의 정의에 부합하지만 후자는 그렇지 않다고 생각합니다. 첫 번째도 마찬가지 이지만 두 번째도 마찬가지 std::has_trivial_destructor<Widget>::value입니다 . 그 의미가 무엇인지 모르겠습니다. :)truefalse
— GManNickG 1

10
가상 소멸자는 결코 사소하지 않습니다.
— Luc Danton

@LucDanton : 눈을 뜨고 코드를 보면 너무 효과적이라고 생각합니다! 수정 해 주셔서 감사합니다.
— GManNickG 1

답변:


103

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

만약 당신의 소멸자가 그렇다면 하워드가 지적한 것처럼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구문은 같은 기능의 생성을 방지 멤버 함수를 추가 복사 생성자 / 지정, 같은 일을 주로있다. 그러나 컴파일러에서 특수 동작을 트리거하기 때문에 기본 생성자 / 소멸자에서도 유용합니다.


2
따라서 주요 문제는 결과 클래스가 사소한 지 여부이며, 그 문제의 기본은 사용자가 선언 한 특수 함수 ( =default함수 의 경우 )와 사용자가 제공 한 함수 (의 경우 {})의 차이입니다. 사용자가 선언 한 함수와 사용자가 제공 한 함수는 다른 특수 멤버 함수의 생성을 막을 수 있지만 (예를 들어, 사용자가 선언 한 소멸자가 이동 작업의 생성을 막는 경우), 사용자가 제공 한 특수 함수 만 클래스를 중요하지 않게 만듭니다. 권리?
— KnowItAllWannabe

@KnowItAllWannabe : 그게 일반적인 생각입니다.
— Nicol Bolas

나는 이것을 생성 된 답변과 하워드의 답변을 참조하여 소멸자를 모두 다루기 때문에 허용되는 답변으로 선택하고 있습니다.
— KnowItAllWannabe

"C ++ 11의 규칙에 관한 한, 사소한 유형의 권리"에 대해서는 누락 된 단어 인 것처럼 보입니다. 나는 그것을 고칠 것이지만 의도 된 것을 100 % 확신하지는 못합니다.
— jcoder

2
= default다른 생성자가 존재하더라도 컴파일러가 기본 생성자를 생성하도록 강제하는 데 유용합니다. 다른 사용자 선언 생성자가 제공되는 경우 기본 생성자가 암시 적으로 선언되지 않습니다.
— bgfvdu3w

42

둘 다 사소하지 않습니다.

베이스와 멤버의 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.

9
흥미롭게도 : =default;컴파일러를 사용하면 소멸자를 사용하지 않는 한 소멸자를 생성하지 않으므로 오류가 발생하지 않습니다. 반드시 버그가 아니더라도 이상하게 보입니다. 나는이 행동이 표준에서 요구 되는 것을 상상할 수 없다 .
— Nik Bougalis

"그런 다음 = default 솔루션이 컴파일됩니다"아닙니다. 방금 테스트
— 나노

오류 메시지는 무엇이며 어떤 버전의 VS입니까?
— Howard Hinnant

35

사이의 중요한 차이점

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


그러면 왜 "{}"및 "= default"가 항상 std :: string ideone.com/LMv5Uf를 초기화 합니까?
— nawfel bgh

1
@nawfelbgh 기본 생성자 A () {}는 비 POD 유형이므로 std :: string에 대한 기본 생성자를 호출합니다. std :: string의 기본 ctor는 비어있는 0 크기 문자열로 초기화 합니다. 스칼라의 기본 ctor는 아무 것도 수행하지 않습니다. 자동 저장 기간이있는 객체 (및 해당 하위 객체)는 불확실한 값으로 초기화됩니다.
— 4pie0
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.