2

この基本クラスがあるとします:

class Foo public: std::exception
{
    public:
        // printf()-style parms are formatted with vsnprintf() into a message
        Foo( const char * format, ... );
        // lots more stuff in this class not relevant to this example
};

今、私はこれを何十もの場所で基本例外クラスとして使用する必要があります。その後、catch() 句を記述して、必要に応じてベース クラスまたは派生クラスのみをキャッチできます。

私がやりたいことは、数十の新しいクラス定義を非常に単純に保つことです。私はC++ 11で継承されたコンストラクターについて読んでいますが、これらの行に沿った何かが必​​要だと思います:

class A : public Foo { public: using Foo::Foo; };
class B : public Foo { public: using Foo::Foo; };

問題は、このプロジェクトが Microsoft Visual Studio を使用して Windows でもコンパイルされていることです。私が知る限り、コンストラクターの継承はサポートされていません

派生クラスを比較的単純に保つためにこれを行う別の明白な方法はありますか? おそらくテンプレートで?私はテンプレートに関しては初心者であり、これが正しい方法である場合、正しい方向にキックを使用できます.

4

3 に答える 3

3

このようなものはどうですか:

class Base : public std::exception {};

template <int UNUSED>
class Foo : public Base
{
    public:
        // printf()-style parms are formatted with vsnprintf() into a message
        Foo( const char * format, ... );
        // lots more stuff in this class not relevant to this example
};

typedef Foo<1> A;
typedef Foo<2> B;

すべての Foo 例外をキャッチできるように基本クラスを追加するように編集されました。

于 2013-10-12T03:40:18.813 に答える
1

継承コンストラクターのポイントは、多くのコンストラクターを継承することであり、この例には 1 つしかありません。それはさておき…</p>

はい、継承コンストラクターが機能しない場合の回避策があります。代わりに完全転送を使用できます。

template< typename ... a >
A( a && ... arg ) : Foo( std::forward< a >( arg ) ... ) {}

欠点は、変換がFoo( … )呼び出しサイトではなく呼び出しサイトで適用されることA( … )です。したがって、転送コンストラクターは、変換を必要とするおそらくより適切なコンストラクターよりも優先されます。また、ブレース初期化リストは、テンプレート推定引数にすることはできませんが、コンストラクターを継承する場合は機能します。また、複数のベースに対してこれを行うことはできませんが、とにかくめったに起こりません.

これは、C スタイルの可変個引数リストまたはその他のもので動作するはずです... ただし、コンパイラのバージョンによっては、そのようなコーナー ケースでの完全な転送も厄介な問題になる場合があります。

于 2013-10-12T01:51:11.433 に答える
1

これには、いくつかの異なるアプローチを考えることができます。

  1. 可変個引数テンプレートを使用するか、それらをエミュレートします。
  2. mixin タイプのテンプレートを使用します。
  3. テンプレートFooクラスを作成します。
  4. 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() {}        
};

クラスの新しい機能をAmix-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::stringas コンストラクターの引数を取るだけです。それがstd::runtime_error仕組みです。関数を有意義に実装するため、直接ではstd::runtime_errorなくから派生することを検討することもできます。std::exceptionwhat()

于 2013-10-12T12:56:51.087 に答える