クラスツリー内のダイナミックコンバージョンにダイナミックキャストを使用することは受け入れられますか?
-
26-10-2019 - |
質問
大学の課題については、一部には、いくつかのクラスを含むクラス構造を構築しています。 Pixel
, 、それぞれが特定の色空間(8ビットグレースケール、24ビットRGBなど)を使用しています。
ほとんどの作業はによって行われます Image::Base&
, 、使用します Pixel::Base&
S、だから特定のタイプのものがわかりません Pixel
任意の両側にあります Pixel
割り当て。
したがって、未定のサブタイプ間の変換を可能にするために、私は変換コンストラクターを利用しています operator=
, 、基本クラスの仮想関数を介して。すべてのクラスが実装する必要がある2つのオプションを見ました to_Grey8()
, to_RGB()
など - コンバージョンコンストラクターを作成します operator=
個別のコンバージョン関数がたくさんあるという犠牲を払って小さい - または、受信オブジェクトに変換自体を実行してもらう(逆の結果がある)。
何らかの理由で、私は最初に後者の選択をしました。
問題は、RGBへの変換(たとえば)が他のオブジェクトから内部値をコピーすることを望んでいることです もしも たまたまRGBオブジェクトでもありますが、他のオブジェクトが単なる場合はそれを行うことはできません Base&
, 、その結果、私は最終的に使用しました dynamic_cast
sチェックする - サブタイプの数が増えるにつれてそれらの多くが潜在的にそれらの多く - 異なるタイプのそれぞれの Pixel
それには特別な取り扱いが必要です。
私はこのアプローチが「悪い」かもしれないと感じているので、:
これは正しい /有効 /安全な使用法ですか dynamic_cast
?
そうでない場合、なぜですか?そして、同じ目標を達成するための合理的な代替手段は何ですか?
ただ集中しています operator=()
, 、ここに定義の重要な部分があります:
class Base
{
public:
virtual Pixel::Base& operator=( Pixel::Base const& rhs ) = 0;
}
class RGB24 : public Base
{
private:
unsigned char r, g, b;
public:
virtual Pixel::RGB24& operator=( Pixel::Base const& rhs );
}
class Grey8: public Base
{
private:
unsigned char i;
public:
virtual Pixel::Grey8& operator=( Pixel::Base const& rhs );
}
このように見える実装で
Pixel::Grey8& Grey8::operator=( Pixel::Base const& rhs )
{
i = rhs.intensity();
return *this;
}
Pixel::RGB24& RGB24::operator=( Pixel::Base const& rhs )
{
try {
auto castrhs = dynamic_cast<Pixel::RGB24 const&>(rhs);
r = castrhs.r;
g = castrhs.g;
b = castrhs.b;
return *this;
} catch (std::bad_cast& e) {}
//TODO other casting blocks will go here as other Pixel classes are added
//no specific handler worked, so just effectively greyscale it
r = rhs.intensity();
g = rhs.intensity();
b = rhs.intensity();
return *this;
}
解決
絶対的な効率が必要ない場合は、変換を2つのステップに分割することを検討してください。ピクセルタイプT1を最も高い色の解像度(RGB48と呼びます)を持つ「ユニバーサル中間」タイプに変換してから、次に変換してから、次に変換してください。そのタイプからT2に変換します。そうすれば、n^2変換関数ではなく、2*n変換関数を記述するだけです。あなたは次のようなものになります:
Pixel::RGB24& RGB24::operator=( Pixel::Base const& rhs )
{
Pixel::RGB48 rgb48pixel = rhs.toRGB48();
r = rgb32pixel.r/256; // downsample
g = rgb32pixel.g/256; // downsample
b = rgb32pixel.b/256; // downsample
}
すべてのピクセルタイプがtorgb48()を定義する必要がある場合 - ベースクラスで抽象的な仮想メソッドとして宣言されます。
元の質問(dynamic_cast ok/safe/etc/etc)への回答では、使用するのが適切な場合がありますが、しばしば「コードの匂い」と見なされます。ただし、本物の複数の派遣がある場合 - つまり、2つのタイプに依存する操作が必要です。 できません 中間の共通表現を使用して2つのステップに分解されると、それが本当に唯一の選択肢です。他にも大丈夫な場合もあります。確かに安全/有効であり、それは時々、最適ではない設計の兆候です。
動的ディスパッチを使用する場合は、次の方法で使用することをお勧めします。
if (auto casthrs = dynamic_cast<Pixel::RGB24 const>(&rhs)) {
r = castrhs->r;
g = castrhs->g;
b = castrhs->b;
return *this;
}
(すなわち、dynamic_castは、参照ではなくポインターをキャストします。これは、例外をスローするのではなく、失敗した場合に0を返します。)その方法では、例外処理のオーバーヘッドを避け、読みやすくなります。
他のヒント
私には、あなたが使用しているツール(オーバーライドに等しく、仮想関数と動的キャストに等しい)は、このプロジェクトに適していないようです。あるタイプのピクセルを別のタイプに変換する場合、おそらく潜在的に大きなビットマップを変換していると思われます。この場合、パフォーマンスが重要です。ピクセルごとではなく、単なるビットマップでコンバージョンを実装します。キャスティングが潜在的に高価な場合、ピクセルごとに発生しないでください。
私はしばらく上記で参照する言語の特徴を捨てることを提案します。この場合、彼らは価値を追加していないと思います。実際、彼らは解決策に問題があると考えています。より原始的なプログラミング構造を使用して、ソリューションを再考します。