2012-01-18 9 views
3

私は次のように過度に深く継承を有するC++プログラムがあるとします深すぎるC++クラス階層でスタックオーバーフローが発生する可能性がありますか?

using namespace std; 

class AbstractParentGeneration0 { 
    private: 
     ... 
    protected: 
     ... 
    public: 
     virtual returnVal funcName(void) = 0; 
}; 

class AbstractParentGeneration1: virtual public AbstractParentGeneration0 { 
    private: 
     ... 
    protected: 
     ... 
    public: 
     virtual returnVal funcName(void) = 0; 
}; 

. 
. 
. 

class AbstractParentGeneration999999999: virtual public AbstractParentGeneration999999998 { 
    private: 
     ... 
    protected: 
     ... 
    public: 
     virtual returnVal funcName(void) = 0; 
}; 

class ChildGeneration: public AbstractParentGeneration999999999 { 
    private: 
     ... 
    protected: 
     ... 
    public: 
     returnVal funcName(void) { ... }; 
}; 

は、(プログラムの性質が深い継承 を圧縮することができないようなものであると仮定し、それは進化の種 系統を表すか、または仮定深いタクソノミー階層)

トップの抽象クラスを呼び出すときにスタックオーバーフローの危険はありませんか?

(「のulimit -sバイト」または 抽象階層を崩壊以外の)どのような戦略C++プログラマは システム境界内で動作するように使うのですか?

RPC経由で多くのホストシステムにわたって深い垂直継承階層を平坦化する方法はありますか?

独自のコールスタックメカニズムを設計する人がいますか?

分散ネットワーク/クラスタコールスタックのようなものはありますか?

+0

+1 StackOverflowsについて質問します。 –

+0

継承の '仮想'側面を取り除きます。クラスのsizeof()は、その下にある基底の数に関係なく変更されます(ユーザー定義のデータメンバーは無視されます)。 )。 – mcmcc

+0

なぜそれを試してみませんか? –

答えて

3

具体的な質問にお答えするには、トップレベル抽象仮想メソッドを呼び出す際に実行時にスタックオーバーフローの危険性がありません。 C++のほとんどの実装では、適切な実装関数を直接指すvtableポインタエントリがオブジェクトインスタンスにあります。

これがどのように機能するのか不思議であれば、いくつかのレベルの階層を持つプログラムを作成し、実際に何が起こっているかを示すアセンブリレベルのデバッガを読み込むことをお勧めします。

+0

誰かがディスパッチテーブルの代わりにB +ツリーディスパッチを実装しましたか(ディスパッチテーブルが使用可能なメモリよりも大きくなった場合に備えて)?バイナリツリーディスパッチを実装しているものもありますが、これはまだメモリに束縛されています。 B +ツリーの利点は、それらが記憶域/メモリをオーバーラップさせ、両方の世界のベストを使用することです(メモリの高速検索と膨大なサイズだが、記憶域の検索速度が遅い)。 –

+0

あなたの*ディスパッチテーブル*が大きすぎてメモリそのものに保持できない状況を想像することはできません。あなたは実際の作業のために残されたスペースはありません! –

6

おそらく、これはコンパイラの内部制約に違反します。問題がある場合、ランタイムは表示されません。

これを解決する一般的な方法は、このようなコードの生成をやめることです。この種のデータをランタイムに焼かないでください。代わりに、クラスファイルではなく、クラスインスタンスの階層に読み込まれ、解析されるファイル形式でなければなりません。

5

トップの抽象クラスを呼び出すときにスタックオーバーフローの危険はありませんか?

あります。各コンストラクタとデストラクタは、親クラスの対応する部分を階層の上まで呼び出して、非常に深い呼び出しスタックを生成します。

構造が残っていれば、仮想関数の呼び出しはうまくいくでしょう。それは最終的なオーバーライドへの関数呼び出しです。

C++プログラマはシステム境界内で作業するためにどのような戦略(ulimit -aまたは抽象的な階層を折りたたむか)を使用しますか?

個人的には、私は抽象的なインターフェースを除いて継承を避ける傾向があります(抽象的なインターフェースを除いて、私は物事をうまくカプセル化するのが面倒です)。

この場合、オブジェクトの実行時階層を示唆するかもしれません。おそらく関数ポインタを使って、仮想関数に同様の動作を与えます。唯一の制約は、それらをすべて保存するために必要なメモリだけです。タイプシステムを使用してカテゴリを表現するのと同じくらいエレガントではありませんが、実装の限界にはあまり慣れていません。

+1

コンパイラにも制限があります。私は、実行時にスタックの問題を引き起こすような深さのクラス階層がコンパイラの制限を超えていると思われます。 (設計上の観点からは、もちろん、4〜5つの深さが妥当である場合もあります。それ以上のことは、マシン生成コードでのみです)。 –

1

はそれを試してみて、自分の目で確かめてください。この単純なケースでは)

template <int i> 
class oferflowMe: public overflowMe<i-1>{ 
    .... 
}; 

template <> 
class oferflowMe<1>{ 
    .... 
}; 
+0

実装定義のテンプレートインスタンス化の制限があります。はるかに低いです。 C++ 03は「少なくとも17」とアドバイスしました! – MSalters

+0

@MSaltersテンプレートを使って静的な計算をしている人が1人から100人までの集計のように見えました。 –

0

ありません。しかし、複数の仮想継承がある場合、コンパイラの中には、 "adjustor thunks"を使ってthisを修正するものがあります。これらの小さな関数はあなたの呼び出しスタックに終わるでしょう。

+0

コンパイラはこれらのサンクを使用しますが、それらはスタックに残りますか?私はむしろテールコールのように機能していると思います。関数は呼び出されており、最後の関数を呼び出すとスタックから消えます。とにかく、それらのサンクも適切な機能ですか? –

関連する問題