12

私はこの記事を書き、それについていくつかのコメントをもらい、私を混乱させました。

基本的にはT2、テンプレートパラメータとしてのみ使用されているのを見て、誤って前方宣言の機会を利用できるという結論にジャンプしました。

struct T2;

struct T1
{
    std::auto_ptr<T2> obj;
};

T2これは、同じTUのどこかを定義しなければ、UBを呼び出します。これは、その内部をstd::auto_ptr<T2>呼び出し、完全な型に重要なデストラクタを持つ不完全な型のオブジェクトへのポインタを呼び出すことが未定義であるためですdeleteT2*delete

[C++11: 5.3.5/5]:削除されるオブジェクトの削除時点でのクラスタイプが不完全であり、完全なクラスに重要なデストラクタまたは割り当て解除関数がある場合、動作は定義されていません。

私がたまたま使用していたGCCツールチェーン— v4.3.3(Sourcery G ++ Lite 2009q1-203)—は親切にも、メモで知らせてくれました。

注:クラスの定義時に宣言されている場合でも、デストラクタもクラス固有の演算子deleteも呼び出されません。

ただし、他のGCCバージョンでこの診断を取得するのは難しいようです。

delete私の不満は、不完全な型のインスタンスへのポインターがUBではなく不正な形式である場合、このようなバグを見つけるのがはるかに簡単であるということでしたが、それは実装が解決するのが難しい問題のようです。それがUBである理由を理解してください。

しかし、std::unique_ptr<T2>代わりに使用する場合、これは安全で準拠していると言われています。

n3035は20.9.10.2で次のように述べているとされています。

のテンプレートパラメータTunique_ptr不完全なタイプである可能性があります。

私がC++11で見つけることができるのは、次のとおりです。

[C++11: 20.7.1.1.1]:

/ 1クラステンプレートdefault_deleteは、クラステンプレートのデフォルトの削除者(破棄ポリシー)として機能しますunique_ptr

/2のテンプレートパラメータTdefault_delete不完全なタイプである可能性があります。

ただし、default_delete'soperator()には完全なタイプが必要です。

[C++11: 20.7.1.1.2/4]:Tが不完全なタイプの場合、プログラムの形式が正しくありません。


私の質問はこれだと思います:

私の記事のコメント投稿者は、次のコードのみで構成される翻訳単位は整形式で明確に定義されていると言っていますか?それとも彼らは間違っていますか?

struct T2;

struct T1
{
    std::unique_ptr<T2> obj;
};

それらが正しい場合、少なくともそれstd::auto_ptrが使用されている場合、それがUBであるという正当な理由があるとすると、コンパイラはこれをどのように実装することが期待されますか?

4

2 に答える 2

9

GOTW#100のハーブサッターによると、不完全なタイプに関してと同じ問題にunique_ptr苦しんでいます。auto_ptr

... unique_ptrとshared_ptrはどちらも不完全な型でインスタンス化できますが、unique_ptrのデストラクタはdeleteを呼び出すために完全な型を必要とします...

彼の提案は、ヘッダーファイルで包含クラス(つまり)のデストラクタを宣言し、その定義を完全な型T1である変換ユニットに配置することです。T2

// T1.h
struct T2;

struct T1
{
  ~T1();
  std::unique_ptr< T2 >;
};

// T1.cpp
#include "T2.h"

T1::~T1()
{
}
于 2012-10-07T00:14:22.147 に答える
8

次の例は、 と の違いを示す試みstd::auto_ptr<T>ですstd::unique_ptr<T>。最初に、2 つのソース ファイルと 1 つのヘッダーで構成されるこのプログラムを考えます。

ヘッダー:

// test.h

#ifndef TEST_H
#define TEST_H

#include <memory>

template <class T>
using smart_ptr = std::auto_ptr<T>;

struct T2;

struct T1
{
    smart_ptr<T2> obj;

    T1(T2* p);
};

T2*
source();

#endif  // TEST_H

最初のソース:

// test.cpp

#include "test.h"

int main()
{
    T1 t1(source());
}

セカンドソース:

// test2.cpp

#include "test.h"
#include <iostream>


struct T2
{
    ~T2() {std::cout << "~T2()\n";}
};

T1::T1(T2* p)
    : obj(p)
{
}

T2*
source()
{
    return new T2;
}

このプログラムはコンパイルされるはずです (警告が表示されてコンパイルされる場合もありますが、コンパイルされるはずです)。しかし、実行時に未定義の動作を示します。そして、おそらく出力されません:

~T2()

T2のデストラクタが実行されていないことを示します。少なくとも私のシステムにはありません。

test.h を次のように変更すると:

template <class T>
using smart_ptr = std::unique_ptr<T>;

次に、コンパイラは診断 (エラー) を出力する必要があります。

つまり、この間違いを犯すとauto_ptr、実行時エラーが発生します。この間違いを犯すとunique_ptr、コンパイル時エラーが発生します。そして、それauto_ptrがとの違いunique_ptrです。

~T1()コンパイル時エラーを修正するには、完了後にアウトラインを作成する必要がありますT2。test2.cpp の後に追加T2:

T1::~T1() = default;

これで、コンパイルして出力する必要があります。

~T2()

move メンバーも同様に宣言およびアウトライン化する必要があります。

T1::T1(T1&&) = default;
T1& T1::operator=(T1&&) = default;

これらと同じ修正を行うことができauto_ptr、それは再び正しいでしょう. auto_ptrしかし、繰り返しになりますが、との違いはunique_ptr、前者では実行時までデバッグを行う必要があることがわかりません (コンパイラーが提供するモジュロ オプションの警告)。後者を使用すると、コンパイル時に確実に見つけることができます。

于 2012-10-07T02:54:51.570 に答える