C++ 11コードには、正しく修正したいという警告がありますが、実際にはその方法がわかりません。 std::runtime_error
から派生した独自の例外クラスを作成しました:
class MyError : public std::runtime_error
{
public:
MyError(const std::string& str, const std::string& message)
: std::runtime_error(message),
str_(str)
{ }
virtual ~MyError()
{ }
std::string getStr() const
{
return str_;
}
private:
std::string str_;
};
/Wall
を使用してclang-clでそのコードをコンパイルすると、次の警告が表示されます。
warning: definition of implicit copy constructor for 'MyError' is deprecated
because it has a user-declared destructor [-Wdeprecated]
したがって、MyError
でデストラクタを定義したため、MyError
のコピーコンストラクタは生成されません。これにより問題が発生するかどうかは完全にはわかりません...
今では、単に仮想デストラクタを削除することでその警告を取り除くことができますが、基本クラス(この場合はstd::runtime_error
)に仮想デストラクタがある場合、派生クラスには仮想デストラクタが必要だと常に考えていました。
したがって、仮想デストラクタを削除するのではなく、コピーコンストラクタを定義することをお勧めします。ただし、コピーコンストラクターを定義する必要がある場合は、コピー割り当て演算子と移動コンストラクターおよび移動割り当て演算子も定義する必要があります。しかし、これは私の単純な例外クラスにとってはやり過ぎのようです!?
この問題を最適に修正する方法はありますか?
派生クラスでデストラクタを明示的に宣言する必要はありません。
§15.4デストラクタ[class.dtor](強調鉱山)
デストラクタは、仮想(13.3)または純粋仮想(13.4)として宣言できます。そのクラスまたは派生クラスのオブジェクトがプログラムで作成された場合、デストラクタが定義されます。クラスに仮想デストラクタを持つ基本クラスがある場合、そのデストラクタ(ユーザー宣言または暗黙宣言)はvirtualです。
実際、デストラクタを明示的に宣言すると、移動コンストラクタおよび移動代入演算子の暗黙的な生成が妨げられるため、場合によってはパフォーマンスに悪影響を与える可能性もあります。
デストラクタで何かを行う必要がない限り、デストラクタの明示的な宣言を単に省略してください。
カスタムのデストラクタが必要で、デフォルトのコピーctor、コピー代入演算子、移動ctor、移動代入演算子が正しいことを行うことが確実な場合は、次のように明示的にデフォルトにすることが最善です。
MyError(const MyError&) = default;
MyError(MyError&&) = default;
MyError& operator=(const MyError&) = default;
MyError& operator=(MyError&&) = default;
これがC++ 98のパーフェクトな有効なコードであったため、エラーが表示される理由についてのいくつかの推論:
C++ 11の時点では、コピーコンストラクターの暗黙的な生成は非推奨として宣言されています。
§D.2コピー関数の暗黙的宣言[depr.impldec]
クラスにユーザー宣言のコピー割り当て演算子またはユーザー宣言のデストラクターがある場合、デフォルトとしてのコピーコンストラクターの暗黙的な定義は非推奨です。クラスにユーザー宣言されたコピーコンストラクターまたはユーザー宣言されたデストラクター(15.4、15.8)がある場合、デフォルトとしてのコピー割り当て演算子の暗黙的な定義は廃止されます。この国際規格の将来の改訂では、これらの暗黙の定義は削除される可能性があります(11.4)。
このテキストの背後にある理論的根拠は、よく知られた3つの規則です。
以下の引用はすべてcppreference.comから引用されています。 https://en.cppreference.com/w/cpp/language/rule_of_three
クラスにユーザー定義のデストラクター、ユーザー定義のコピーコンストラクター、またはユーザー定義のコピー割り当て演算子が必要な場合、ほぼ確実に3つすべてが必要です。
この経験則が存在する理由は、デフォルトで生成されるdtor、コピーctor、およびさまざまなタイプのリソース(特にメモリへのポインターだけでなく、ファイル記述子やネットワークソケットなど、他のカップルに名前を付けるなど)を割り当てるための割り当て演算子がほとんど生成されないためです正しい振る舞いをしてください。プログラマーがクラスデストラクターのファイルハンドルを閉じるために特別な処理が必要だと思った場合、このクラスをコピーまたは移動する方法を定義したいでしょう。
完全を期すために、以下はしばしば関連する5の規則と、やや議論の余地のないゼロの規則
ユーザー定義のデストラクタ、コピーコンストラクタ、またはコピー割り当て演算子の存在により、移動コンストラクタおよび移動割り当て演算子の暗黙的な定義が防止されるため、移動セマンティクスが望ましいクラスは、5つの特別なメンバー関数すべてを宣言する必要があります。
カスタムデストラクタ、コピー/移動コンストラクタ、またはコピー/移動割り当て演算子を持つクラスは、所有権のみを扱う必要があります(これは単一責任原則に従っています)。他のクラスには、カスタムデストラクタ、コピー/移動コンストラクタ、またはコピー/移動代入演算子を使用しないでください。
今、私は単に仮想デストラクタを削除することでその警告を取り除くことができましたが、ベースクラス(この場合はstd :: runtime_error)に仮想デストラクタがある場合、派生クラスには仮想デストラクタが必要だといつも思っていました。
あなたは間違っていると思った。派生クラスは、明示的に作成したかどうかに関係なく、ベースで定義した場合、常に仮想デストラクターを持ちます。したがって、デストラクタを削除するのが最も簡単な解決策です。 documentation for std::runtime_exception
独自のデストラクタも提供しません。ベースクラスstd::exception
には仮想dtorがあります。
ただし、デストラクタが必要な場合は、コンパイラによって生成されたコピーctorを明示的に追加できます。
MyError( const MyError & ) = default;
またはクラスをコピー不可にすることを禁止します:
MyError( const MyError & ) = delete;
代入演算子についても同じです。