これには、いくつかの異なるアプローチを考えることができます。
- 可変個引数テンプレートを使用するか、それらをエミュレートします。
- mixin タイプのテンプレートを使用します。
- テンプレート
Foo
クラスを作成します。
- C スタイルの可変引数コンストラクターは避けてください。
ここにスポイラーがあります: C スタイルの可変引数コンストラクターを避けることをお勧めします。すぐに答えが欲しい人は、以下のポイントを読むだけで十分です.
可変個引数テンプレート
コードは次のようになります
class A : virtual public Foo
{
public:
template <typename Ts>
explicit A( Ts&&...ts ) : Foo( std::forward<Ts>(ts)... ) {}
};
が受け取るすべての引数A::A
は、単純に のコンストラクターに転送されFoo
ます。実際には、継承コンストラクターと同じです。残念ながら、VS10 も VS11 も可変引数テンプレートをまだサポートしていません。しかし、これをエミュレートする方法があります:
class A : virtual public Foo
{
public:
template <typename T1>
explicit A( T1 && t1 )
: Foo( std::forward<T1>(t1)
) {}
template <typename T1
, typename T2>
A( T1 && t1
, T2 && t2 )
: Foo( std::forward<T1>(t1)
, std::forward<T2>(t2)
) {}
template <typename T1
, typename T2
, typename T3>
A( T1 && t1
, T2 && t2
, T3 && t3 )
: Foo( std::forward<T1>(t1)
, std::forward<T2>(t2)
, std::forward<T3>(t3)
) {}
// and so forth, until a certain limit
};
私はこれが醜いことを知っています。しかし、標準ライブラリの実装でさえ、この肥大化した手法を使用しています。おそらく、作成するすべての例外クラスに対してこれを行う必要はありません。代わりに、これから説明するように、実装を行うテンプレート クラスを使用してこれを行うことができます。
ミックスインタイプのテンプレート
クラスごとに上記のコードが肥大化するのを避けるために、代わりにテンプレート クラスに作業を任せることができます。
template <typename Mixin>
class FooImpl : virtual public Foo, public Mixin
{
public:
template <typename Ts>
explicit FooImpl( Ts&&...ts )
: Foo( std::forward<Ts>(ts)... ), Mixin() {}
};
クラスの新しい機能をA
mix-in クラスにAMixin
入れると、次のように記述できます。
typedef FooImpl<AMixin> A;
必要な機能が得られます。もちろん、可変個引数テンプレートがないため、肥大化したコードを一度使用する必要があります。しかし、それは一度だけです。Foo
また、そのクラスの機能が必要な場合は、ミックスイン クラスに から仮想的に継承させることができます。
class AMixin : virtual public Foo
{
AMixin() : Foo( "" ) {}
// new functionality
};
仮想継承には、最も派生したクラスが呼び出されるコンストラクターを決定するという素晴らしい副作用がありFoo
ます。この場合、それはFooImpl
テンプレート クラスになります。したがって、 について心配する必要はありませんFoo( "" )
。
Foo
テンプレートクラスを作る
別のアプローチFoo
は、実装を行うテンプレート クラスを作成することです。
template <typename Tag>
class Foo : public std::exception
{
public:
Foo( const char * s, ... );
// other functionality
};
typedef Foo<struct ATag> A;
typedef Foo<struct BTag> B;
このアプローチには、ミックスイン アプローチに比べていくつかの欠点があります。
- 共通基盤がありません。例外をキャッチすることはできません
Foo
が、特定の派生クラスのみをキャッチできます。
- mix-in クラスを使用して派生クラスに機能を追加することはできません。
必要に応じて Foo クラスを拡張して機能をカバーできるため、2 番目のポイントはそれほど悪くないかもしれません。多くの場合、タイプは例外クラスのユーザーにとって十分な情報です。最初の点については、解決策があります。Foo
を継承するテンプレート クラスの共通基本クラスを作成しますstd::exception
。
class AbstractFoo : public std::exception
{
public:
// other functionality from above,
// possibly some pure virtual functions.
// constructors will be generated by the compiler.
};
template <typename Tag>
class Foo : public AbstractFoo
{
public:
Foo( const char * s, ... );
// other functionality
};
typedef Foo<struct ATag> A;
typedef Foo<struct BTag> B;
AbstractFoo
これで、クライアント コードは、テンプレートのインスタンス化の共通ベースをキャッチできます。この場合、クライアントは参照によってキャッチする必要があることに注意してください。これは正しい方法であるため、これは良いことです。(そうしないと、型スライスの問題が発生します。)
C スタイルの可変引数コンストラクターを避ける
C スタイルの可変個引数関数は型安全ではありません。特に、通常最もテストされていないコードであるエラー処理コードでは、エラーが発生しやすい (コンパイル時エラーではなく実行時エラーが発生する) ため、これは避けるべきものです。したがって、コンパイル時にエラーを生成し、これらの可変引数コンストラクターをまとめて回避するプログラミング手法を優先してください。可変数の引数を渡す代わりに、std::string
. 呼び出し元は、文字列を簡単にまとめることができます。
// Your code
class Foo : public std::exception
{
Foo( std::string message );
// other stuff
};
// client code
if ( error )
throw Foo( "Could not open file '" + fileName + "'." );
派生クラスは、そのstd::string
引数を基本クラスに単純に転送できますFoo
。"%s"
クライアント コードは、私が個人的に醜いと思うこの C スタイルの書式設定リストがなくても、きれいでシンプルに見えます。おそらく、このアプローチに異議を唱える可能性があります。
- いくつかの数字を印刷したい場合はどうすればよいですか? では、 を使用します
std::to_string(i)
。
- 文字列の構築中に例外がスローされた場合はどうなりますか? それは可能ですが、可能性は非常に低いです。どのような例外がスローされる可能性がありますか? おそらく
std::bad_alloc
?私は他に何も考えられません。文字列を構築するために、ヒープに数バイトを割り当てる必要があります。例外がスローされる可能性はほとんどありません。しかし、考えてみてください。これは、クラス内でスローされる可能性のあるものを使用していない場合でも、可変個引数の実装でも発生する可能性があります。クライアント コードが例外をスローしようとすると、例外は概念的に未定義の場所にコピーされます (C++ 標準に従って)。この未定義の場所はヒープ (確かにスタックではありません) である可能性があり、メモリ不足になる可能性があります。std::bad_alloc
例外の代わりにスローされます。したがって、例外の作成またはコピー中にクライアント コードで例外がスローされる可能性を回避することはできません。
私のアドバイス:std::string
as コンストラクターの引数を取るだけです。それがstd::runtime_error
仕組みです。関数を有意義に実装するため、直接ではstd::runtime_error
なくから派生することを検討することもできます。std::exception
what()