web-dev-qa-db-ja.com

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

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

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

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

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

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

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

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

Widget() = default;

の代わりに

Widget() {}

元の投稿がいくつかのSOルールに違反している後、この質問を拡張した場合、謝罪します。

146

これは、コンストラクタについてデストラクタについて尋ねる場合、まったく異なる質問です。

デストラクタがvirtualの場合、違いはごくわずかです ハワードが指摘したように 。ただし、デストラクタがnon-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構文は、コピーコンストラクター/割り当てなどの機能を実行するために主にあり、そのような関数の作成を妨げるメンバー関数を追加します。ただし、コンパイラからの特別な動作もトリガーするため、デフォルトのコンストラクタ/デストラクタでも役立ちます。

87
Nicol Bolas

どちらも重要です。

どちらも、ベースとメンバーの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、ser-providedデストラクターを指定すると、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.
38
Howard Hinnant

間の重要な違い

_class B {
    public:
    B(){}
    int i;
    int j;
};
_

そして

_class B {
    public:
    B() = default;
    int i;
    int j;
};
_

B() = default;で定義されたデフォルトコンストラクターはnot-user definedと見なされることです。これは、value-initializationの場合のように

_B* pb = new B();  // use of () triggers value-initialization
_

コンストラクターをまったく使用しない特別な種類の初期化が行われ、組み込み型の場合、これはzero-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

30
4pie0