기본 생성자와 소멸자에서 "= 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.