「= default」はデフォルトのコンストラクタとデストラクタの「{}」とどう違うのですか?


169

もともとはデストラクタについての質問としてこれを投稿しましたが、今はデフォルトのコンストラクタの考慮を追加しています。元の質問は次のとおりです。

クラスに仮想のデストラクタを与えたいが、それ以外はコンパイラが生成するものと同じである場合は、次のように使用できます=default

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

しかし、空の定義を使用して入力を減らすことで同じ効果を得ることができるようです:

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

これらの2つの定義が異なる動作をする方法はありますか?

この質問に投稿された回答に基づくと、デフォルトのコンストラクタの状況は似ているようです。デストラクタの「=default」と「{}」の意味にほとんど違いがないことを考えると、デフォルトのコンストラクタのこれらのオプションの意味にも同様にほとんど違いはありませんか?つまり、そのタイプのオブジェクトが作成されて破壊されるタイプを作成したいと仮定すると、なぜ私は言いたいのですか?

Widget() = default;

の代わりに

Widget() {}

元の投稿後にこの質問を拡張すると、SOのルールに違反することになります。デフォルトのコンストラクタについてほぼ同じ質問を投稿することは、あまり望ましくない選択肢であると私を思いました。


1
私が知っていることではありませんが、= defaultより明示的なimoであり、コンストラクターによるそれのサポートと一貫しています。
クリス、

11
確かではありませんが、前者は「自明なデストラクタ」の定義に準拠していると思いますが、後者はそうではありません。最初std::has_trivial_destructor<Widget>::valueはそうですtruefalse、2番目はそうです。それが何を意味するのか私にもわかりません。:)
GManNickG

10
仮想デストラクターは決して簡単なものではありません。
Luc Danton、2012年

@LucDanton:目を開けてコードを見てもうまくいくと思います!修正していただきありがとうございます。
GManNickG

回答:


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 2012年

@KnowItAllWannabe:はい、それが一般的な考えです。
Nicol Bolas、

私はこれを受け入れられた回答として選択します。これは、コンストラクタと(ハワードの回答を参照して)デストラクタの両方をカバーしているためです。
KnowItAllWannabe 2012年

「C ++ 11のルールに関する限り、平凡なタイプの権利に関しては」ここで欠けている単語のようです。私はそれを修正しますが、何が意図されていたのか100%確信できません。
jcoder

2
= default他のコンストラクターが存在するにもかかわらず、コンパイラーにデフォルトのコンストラクターを強制的に生成させるのに役立つようです。他のユーザー宣言コンストラクターが提供されている場合、デフォルトのコンストラクターは暗黙的に宣言されません。
bgfvdu3w 2017

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.
nanoで

エラーメッセージは何でしたか、VSのバージョンは何ですか?
ハワードヒンナン

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

コンストラクタをまったく使用しない特別な種類の初期化が行われ、組み込み型の場合はゼロ初期化になります。この場合はB(){}行われません。C ++標準n3337 § 8.5 / 7は述べています

タイプTのオブジェクトを値初期化するとは、次のことを意味します。

— Tが(おそらくcv修飾された)クラス型(節9)であり、ユーザー提供のコンストラクター (12.1)がある場合、Tのデフォルトコンストラクターが呼び出されます(Tにアクセス可能なデフォルトコンストラクターがない場合、初期化は正しくありません。 );

— Tがユーザー指定のコンストラクターのない(場合によってはcv修飾された)非ユニオンクラス型 である場合、オブジェクトはゼロで初期化され、Tの暗黙的に宣言されたデフォルトコンストラクターが自明でない場合、そのコンストラクターが呼び出されます。

— Tが配列型の場合、各要素は値で初期化されます。それ以外の場合、オブジェクトはゼロで初期化されます。

例えば:

#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 2016

1
@nawfelbghこれは非PODタイプであるため、デフォルトコンストラクターA(){}はstd :: stringのデフォルトコンストラクターを呼び出します。std :: stringのデフォルトのctorは、空の0サイズの文字列に初期化します。スカラーのデフォルトのctorは何もしません。自動ストレージ期間を持つオブジェクト(およびそのサブオブジェクト)は、不確定な値に初期化されます。
4pie0
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.