나는 왜 내가 이것을 할 것인지 이해하지 못한다.
struct S {
int a;
S(int aa) : a(aa) {}
S() = default;
};
왜 그렇게 말하지 않습니까?
S() {} // instead of S() = default;
왜 새로운 구문을 가져 오나요?
답변
기본 기본 생성자는 초기화 목록이없고 빈 복합 명령문이있는 사용자 정의 기본 생성자와 동일하게 정의됩니다.
§12.1 / 6 [class.ctor] 기본값으로 삭제 된 것으로 정의되지 않은 기본 생성자는 클래스 유형의 객체를 만들기 위해 odr-used를 사용하거나 첫 번째 선언 후 명시 적으로 기본값을 지정할 때 암시 적으로 정의됩니다. 내재적으로 정의 된 기본 생성자는 ctor-initializer (12.6.2)가없고 빈 복합 명령문이없는 해당 클래스에 대해 사용자 작성 기본 생성자가 수행하는 클래스의 초기화 세트를 수행합니다. […]
그러나 두 생성자 모두 동일하게 작동하지만 빈 구현을 제공하면 클래스의 일부 속성에 영향을줍니다. 사용자 정의 생성자를 제공하면 아무 것도 수행하지 않아도 유형이 집계 되지 않고 사소 하지 않습니다 . 클래스가 집계 또는 사소한 유형 (또는 일시적 유형, POD 유형)이되도록하려면을 사용해야 = default
합니다.
§8.5.1 / 1 [dcl.init.aggr] 집합은 사용자가 제공 한 생성자가없는 배열 또는 클래스입니다.
§12.1 / 5 [class.ctor] 기본 생성자는 사용자가 제공하지 않고 […]
§9 / 6 [클래스] 사소한 클래스는 사소한 기본 생성자가 있고 […]
시연하려면 :
#include <type_traits>
struct X {
X() = default;
};
struct Y {
Y() { };
};
int main() {
static_assert(std::is_trivial<X>::value, "X should be trivial");
static_assert(std::is_pod<X>::value, "X should be POD");
static_assert(!std::is_trivial<Y>::value, "Y should not be trivial");
static_assert(!std::is_pod<Y>::value, "Y should not be POD");
}
또한 constexpr
암시 적 생성자가 있었을 경우 생성자를 명시 적으로 기본값으로 지정 하여 암시 적 생성자가 가질 수있는 것과 동일한 예외 사양을 제공합니다. 주어진 경우 암시 적 생성자는 constexpr
(데이터 멤버가 초기화되지 않은 상태로 남아 있기 때문에) 되지 않았으며 빈 예외 사양도 있으므로 차이가 없습니다. 그러나 예, 일반적인 경우 수동으로 지정할 수 있습니다constexpr
암시 적 생성자와 일치하도록 예외 사양을 .
사용 = default
은 복사 / 이동 생성자와 소멸자와 함께 사용할 수도 있기 때문에 약간의 균일 성을 가져옵니다. 예를 들어 빈 복사본 생성자는 기본 복사본 생성자와 동일하지 않습니다 (구성원의 멤버 별 복사를 수행함). 이러한 특수 멤버 함수 각각에 대해 = default
(또는 = delete
) 구문을 균일하게 사용하면 의도를 명시 적으로 지정하여 코드를 쉽게 읽을 수 있습니다.
답변
차이점을 보여주는 예가 있습니다.
#include <iostream>
using namespace std;
class A
{
public:
int x;
A(){}
};
class B
{
public:
int x;
B()=default;
};
int main()
{
int x = 5;
new(&x)A(); // Call for empty constructor, which does nothing
cout << x << endl;
new(&x)B; // Call for default constructor
cout << x << endl;
new(&x)B(); // Call for default constructor + Value initialization
cout << x << endl;
return 0;
}
산출:
5
5
0
우리가 볼 수 있듯이 빈 A () 생성자에 대한 호출은 멤버를 초기화하지 않지만 B ()는 호출합니다.
답변
n2210 은 몇 가지 이유를 제공합니다.
기본값 관리에는 몇 가지 문제가 있습니다.
- 생성자 정의가 결합됩니다. 생성자를 선언하면 기본 생성자가 표시되지 않습니다.
- 소멸자는 기본적으로 다형성 클래스에 적합하지 않으므로 명시 적 정의가 필요합니다.
- 디폴트가 억제되면,이를 부활시킬 수단이 없습니다.
- 기본 구현은 종종 수동으로 지정된 구현보다 더 효율적입니다.
- 기본이 아닌 구현은 사소한 것이 아니며 형식 의미에 영향을 미칩니다. 예를 들어 형식을 비 POD로 만듭니다.
- (사소한) 대용을 선언하지 않고 특수 멤버 함수 또는 전역 연산자를 금지 할 수단은 없습니다.
type::type() = default; type::type() { x = 3; }
경우에 따라 기본 멤버가 추가 멤버 선언으로 변경되므로 멤버 함수 정의를 변경하지 않고도 클래스 본문이 변경 될 수 있습니다.
참조 규칙의-세 규칙의-다섯 C ++ 11이됩니까? :
다른 특수 멤버 함수를 명시 적으로 선언하는 클래스에 대해서는 이동 생성자 및 이동 대입 연산자가 생성되지 않으며, 이동 생성자 또는 이동을 명시 적으로 선언하는 클래스에 대해서는 복사 생성자 및 복사 대입 연산자가 생성되지 않습니다. 할당 연산자, 명시 적으로 선언 된 소멸자와 암시 적으로 정의 된 복사 생성자 또는 암시 적으로 정의 된 복사 할당 연산자가있는 클래스는 더 이상 사용되지 않는 것으로 간주됩니다.
답변
경우에 따라 의미론의 문제입니다. 기본 생성자는 명확하지 않지만 다른 컴파일러 생성 멤버 함수에서는 분명합니다.
기본 생성자의 경우 빈 본문이있는 기본 생성자를 사용하는 것처럼 간단한 생성자가 될 수 있습니다 =default
. 결국, 오래된 빈 기본 생성자는 적법한 C ++ 입니다.
struct S {
int a;
S() {} // legal C++
};
컴파일러가이 생성자를 사소한 것으로 이해하는지 여부는 대부분의 경우 최적화 외부 (수동 또는 컴파일러) 외부와 관련이 없습니다.
그러나 이렇게하면 빈 함수 본문을 “기본”으로 처리하려고하면 다른 유형의 멤버 함수에 대해 완전히 분류됩니다. 복사 생성자를 고려하십시오.
struct S {
int a;
S() {}
S(const S&) {} // legal, but semantically wrong
};
위의 경우 빈 본문으로 작성된 복사 생성자가 잘못되었습니다 . 더 이상 실제로는 아무것도 복사하지 않습니다. 이것은 기본 복사 생성자 의미와는 매우 다른 의미의 집합입니다. 원하는 동작을하려면 코드를 작성해야합니다.
struct S {
int a;
S() {}
S(const S& src) : a(src.a) {} // fixed
};
그러나이 간단한 경우에도 컴파일러가 복사 생성자가 자체 생성자와 동일한 지 확인하거나 복사 생성자가 사소한 것임을 확인하는 것은 훨씬 더 많은 부담이 됩니다.memcpy
기본적으로 ). 컴파일러는 각 멤버 이니셜 라이저 표현식을 확인하고 소스의 해당 멤버에 액세스하는 식과 동일한 지 확인해야하며, 기본 구성이 아닌 다른 멤버가 없는지 확인하십시오. 컴파일러는이 함수의 자체 생성 버전이 사소한 지 확인하는 데 사용합니다.
그런 다음 특히 사소하지 않은 경우에는 더 할당 할 수있는 복사 할당 연산자를 고려하십시오. 많은 클래스에 대해 작성하고 싶지는 않지만 C ++ 03에서는 강제로 사용됩니다.
struct T {
std::shared_ptr<int> b;
T(); // the usual definitions
T(const T&);
T& operator=(const T& src) {
if (this != &src) // not actually needed for this simple example
b = src.b; // non-trivial operation
return *this;
};
이것은 간단한 경우이지만, T
(특히 우리가 작업을 믹스로 이동 시키면) 간단한 유형으로 작성하고 싶었던 것보다 이미 더 많은 코드 입니다. 빈 바디는 이미 완벽하게 유효하고 명확한 의미를 갖기 때문에 “디폴트 채우기”를 의미하는 빈 바디에 의존 할 수 없습니다. 실제로 빈 본문이 “기본값 채우기”를 표시하는 데 사용 된 경우 no-op 복사 생성자 등을 명시 적으로 만들 수있는 방법이 없습니다.
다시 일관성의 문제입니다. 빈 본문은 “아무것도하지 않음”을 의미하지만 복사 생성자와 같은 것들에 대해서는 “아무것도하지 않음”을 원하지 않고 오히려 “억제되지 않은 경우 일반적으로하는 모든 것을하십시오”를 의미합니다. 따라서 =default
. 그것은의 필요에 복사 / 이동 생성자와 대입 연산자처럼 억제 컴파일러가 생성 멤버 함수를 극복. 그런 다음 기본 생성자에서도 작동하게하는 것은 “분명한”것입니다.
빈 몸체를 가진 기본 생성자를 만드는 것이 좋을 수도 있고, 사소한 멤버 / 기본 생성자 =default
를 오래된 코드를 더 최적으로 만드는 경우 와 마찬가지로 사소한 것으로 간주 되지만 사소한 코드에 의존하는 대부분의 저수준 코드 최적화를위한 기본 생성자도 간단한 복사 생성자에 의존합니다. 이전의 모든 복사 생성자를 “고정”해야하는 경우, 모든 이전 기본 생성자를 모두 수정해야하는 것은 그리 쉬운 일이 아닙니다. =default
의도를 나타 내기 위해 명시 적을 사용하는 것이 훨씬 명확하고 분명 합니다.
컴파일러에서 생성 한 멤버 함수가 지원을 위해 명시 적으로 변경해야 할 몇 가지 사항이 있습니다. constexpr
기본 생성자를 지원 하는 것이 한 예입니다. =default
다른 모든 특수 키워드 를 사용 하여 함수를 마크 업 하는 것보다 =default
C ++ 11의 테마 중 하나 인 기능을 표시 하는 것 보다 정신적으로 사용 하는 것이 더 쉽습니다. 언어를 더 쉽게 만듭니다. 여전히 많은 사마귀와 역전 타협이 있지만 사용 편의성에 관해서는 C ++ 03에서 크게 발전한 것이 분명합니다.
답변
사용 중단 std::is_pod
및 대안 으로 인해 std::is_trivial && std::is_standard_layout
@JosephMansfield의 답변은 다음과 같습니다.
#include <type_traits>
struct X {
X() = default;
};
struct Y {
Y() {}
};
int main() {
static_assert(std::is_trivial_v<X>, "X should be trivial");
static_assert(std::is_standard_layout_v<X>, "X should be standard layout");
static_assert(!std::is_trivial_v<Y>, "Y should not be trivial");
static_assert(std::is_standard_layout_v<Y>, "Y should be standard layout");
}
(가) 있습니다 Y
아직 표준 레이아웃입니다.