私はなぜこれをするのか理解できません:
struct S {
int a;
S(int aa) : a(aa) {}
S() = default;
};
なぜただ言ってはいけない:
S() {} // instead of S() = default;
なぜそのための新しい構文を導入するのですか?
私はなぜこれをするのか理解できません:
struct S {
int a;
S(int aa) : a(aa) {}
S() = default;
};
なぜただ言ってはいけない:
S() {} // instead of S() = default;
なぜそのための新しい構文を導入するのですか?
回答:
デフォルトのデフォルトコンストラクターは、初期化リストと空の複合ステートメントを持たないユーザー定義のデフォルトコンストラクターと同じであると明確に定義されています。
§12.1/ 6 [class.ctor]デフォルトで削除済みとして定義されていないデフォルトのコンストラクターは、クラス型のオブジェクトを作成するためにodrを使用するとき、または最初の宣言の後に明示的にデフォルトになるときに暗黙的に定義されます。暗黙的に定義されたデフォルトコンストラクターは、クラスの初期化セットを実行します。これは、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
)構文を均一に使用すると、意図を明示的に示すことでコードが読みやすくなります。
constexpr
(7.1.5)を満たす場合、暗黙的に定義されたデフォルトコンストラクターはconstexpr
です。」
constexpr
場合、(a)暗黙の宣言がそうである場合は暗黙的に見なされます。(b)同じであると暗黙的に見なされます。暗黙的に宣言されたかのように例外仕様(15.4)、... "この特定のケースでは違いはありませんが、一般にfoo() = default;
に比べてわずかに有利foo() {}
です。
constexpr
(データメンバーが初期化されていないため)暗黙の宣言は行われず、その例外指定はすべての例外を許可するため、違いはありません。私はそれをより明確にします。
私は違いを示す例があります:
#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
で実行= default
するのではなく、一般的に実行する理由です{ }
。
{}
が導入される前はすでに言語の機能だったため=default
、これらの理由は暗黙的に区別に依存しています(たとえば、[[抑制されたデフォルト]を復活させる手段{}
がない)とは、デフォルトと同等ではないことを意味します)。
場合によっては、セマンティクスの問題です。デフォルトのコンストラクターではそれほど明白ではありませんが、他のコンパイラー生成メンバー関数では明白になります。
デフォルトのコンストラクターの場合、空のボディを持つデフォルトのコンストラクターを、を使用する場合と同じように、自明なコンストラクターの候補と見なすことができます=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
(特に、操作をミックスに移動すると)強制的に作成する必要があるよりも多くのコードになっています。空のボディはすでに完全に有効であり、明確な意味があるため、「デフォルトを埋める」という意味の空のボディに依存することはできません。実際、空のボディが「デフォルトを埋める」ことを示すために使用された場合、何もしないコピーコンストラクタなどを明示的に作成する方法はありません。
これも一貫性の問題です。空のボディは「何もしない」を意味しますが、コピーコンストラクターのようなものでは、「何もしない」ことを本当に望まず、むしろ「抑制されない場合に通常行うすべてのことを行います」したがって=default
。それはだ、必要なコピー/移動コンストラクタと代入演算子のような抑制コンパイラで生成されたメンバ関数を克服するために。その場合、デフォルトのコンストラクターでも機能するように「自明」です。
空のボディと単純なメンバー/ベースコンストラクターを持つデフォルトのコンストラクターも=default
、古いコードをより最適化する場合と同じように、単純なものと見なされるのは良いことかもしれませんが、ほとんどの低レベルのコードは単純なものに依存しています最適化のデフォルトコンストラクターも、単純なコピーコンストラクターに依存しています。古いコピーコンストラクターをすべて「修正」する必要がある場合は、古いデフォルトコンストラクターもすべて修正する必要はありません。また、明示=default
を使用して意図を示すことで、より明確で明白になります。
コンパイラによって生成されたメンバー関数が実行する他のいくつかのことも、サポートに明示的に変更を加える必要があります。constexpr
デフォルトのコンストラクタのサポートはその一例です。=default
他のすべての特別なキーワードなど=default
、C ++ 11の暗黙のテーマである関数をマークアップするよりも、精神的に使用する方が簡単です。つまり、言語をより簡単にします。それはまだたくさんのいぼと逆互換性の妥協を得ていますが、使いやすさに関してはC ++ 03からの大きな前進であることは明らかです。
= default
するところに問題があり、そうa=0;
ではありませんでした!私は賛成してそれを落とさなければなりませんでした: a(0)
。私はまだどれほど便利で= default
あるかについて混乱しています、それはパフォーマンスについてですか?使用しないとどこか壊れます= default
か?私はここですべての回答を読んでみましたが、私はいくつかのc ++に慣れていないので、それを理解するのに多くの問題を抱えています。
a=0
例は別の(関連している)トピックである自明な型の振る舞いのためです。
= default
、それでも付与a
されることを意味します=0
か 何らかの方法で?「コンストラクター= default
を用意してフィールドを適切に初期化する方法」のような新しい質問を作成できると思いますか、ところでstruct
、a class
ではなくa で問題があり、アプリをを使用しなくても正しく実行されてい= default
ます。それが良いものであれば、その質問に最小限の構造体を追加します:)
struct { int a = 0; };
記述します。コンストラクタが必要だと判断した場合は、それをデフォルトにできますが、型は簡単ではないことに注意してください(これは問題ありません)。
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
、まだ標準レイアウトのです。
default
は新しいキーワードではなく、すでに予約済みのキーワードの新しい使用法にすぎません。