0

次のように、基本クラス B からクラス D を派生させました。

class D : public B
{
//staff or nothing
}

B* b受信したポインターを宣言し 、 からのすべての同じプロパティ値でD* d初期化する方法で処理したいと思います。実装を変更する権利がない場合、何が可能または最善の方法ですか? 何かのようなもの*d*bB

// I have pointer to B object, B* b received as a result of some function
class D : public B
{
//staff or nothing
}
D *d; //declaration 
// I want to initialize d, something like d=b (if that would be legal)
// or d = static_cast <D*>(b);

ありがとう

4

2 に答える 2

1

基本的な答え

表記:

B b;
B* pb = &b;
D* pd;

ポインターがあり、 と同じ (の値) データ メンバーをpb持つポインターが必要な場合は、データをある (メモリ) 場所にコピーまたは移動し、その場所を指すようにする必要があります。pd*pd*pb*pbpd

C++ では、暗黙的 ( ) にも明示的 ( )&bにも変換できず、結果として未定義の動作が発生します。未定義の動作は、次に何が起こるか (クラッシュ、データ破損、ハイゼンバグなど) の保証がないため、可能な限り悪いものです。pdpd = &bpd = static_cast<D*>(&b)


static_castptr 変換について

物事をより明確にするために、標準からいくつかの命名を導入しましょう。

B* pb = &d;

次に、の静的タイプ*pbis ですBが、動的タイプ*pbisDです。静的型は基本的にコンパイラが認識するものであり、動的型は実行時に実際に存在するものです。

次の変換は問題ありません。

D d;
pb = &d;  // `pb` points to `B`-type subobject of `d`
pd = static_cast<D*>(pb);  // reverts the cast above to get the original `&d`

*pb動的型D(または の派生型) がある場合は、最後の行で問題ありませDん。それ以外の場合、未定義の動作。それが理由dynamic_castです:

pd = dynamic_cast<D*>(pb);

繰り返します*pbが、 が dynamic type の場合D、すべて問題ありません (上記と同じ)。ただし、*pbが動的タイプDでない場合はdynamic_cast、NULL ポインター値 ( nullptr) を返します。このように、未定義の動作はなく、現在かどうかを確認できpdますnullptrdynamic_castは よりかなり遅いため、変換が成功することが本当にわかっstatic_castている場合は、代わりに を使用できます ( Qt の例を参照)。static_cast


データを新しいオブジェクトにコピーまたは移動する

pdすべてのデータ メンバー (「プロパティ」) が のデータ メンバーと同じになるようなポインターが必要な場合はどうすればよいでしょう&bか。タイプのオブジェクトを作成しD、データ&bをこの新しいオブジェクトにコピーまたは移動する必要があります。これは、方法 2 で田代昴が提案したものです。

class D : public B
{
public:
    // copy:
    D(B const& b) : B(b) {} // if you cannot use B-copy-ctor than have to provide own definition
    // move:
    D(B&& b) : B(b) {} // same issue as above
};

B b;
B* pb = &b;

D d1(*pb);  // this is how you can copy
D d2( std::move(*pb) );  // this is how you can move

D* pd = &d1; // or = &d2;

copy と move の両方の行が、コピー/移動されたデータを格納する新しいDタイプのオブジェクトを作成することに注意してください。コピーと移動の両方のサポートを提供する必要はありません。

-type オブジェクトをラップするなど、他の可能性もありBますが、派生クラスのアプローチとは異なります。


pd = &b変換が悪い理由のいくつかの発言

  • まず第一に、D子クラスで導入されたデータ メンバーが問題であるという点で、田代昴は正しいです。クラスがデータ メンバーを追加する場合、DC++ / コンパイラはそれらの初期化方法をどのように知る必要がありますか? エラーが発生しやすいため、初期化されていないままにしておくことはできません (クラスの不変条件を考慮してください)。
  • メモリの問題。例: D-type オブジェクトがメモリ内で次のようになると想像してください。

    |DD[BBBB]DDDDDDDD|
        ^start of B sub-object
     ^start of D object
    

    これで、 にpd = static_cast<D*>(pb)含まれるアドレスは B サブオブジェクトの開始pbとして解釈され、D オブジェクトの開始を取得するために 2 バイト減少します。

    • オブジェクトにスペースを割り当てただけなので、 からへbのメモリへのアクセスのみが保証されます( 内のアラインメントは考慮されません)。前後にメモリにアクセスするとエラーが発生する可能性があります。たとえば、物理メモリにマップされていない仮想メモリにアクセスすると CPU が不平を言います。の前後に他の意味のあるデータがある可能性が高くなります。クラスで導入された のデータ メンバーに書き込むことにより、この他のデータを破損する可能性があります (他のデータ メンバーについては不明)。(char*)pb(char*)pb + sizeof(b)b(char*)pb(char*)pb + sizeof(b)(char*)pb(char*)pb + sizeof(b)static_cast<D*>(pb)D
    • を介して任意のメンバーにアクセスするときにアライメントの問題が発生する可能性が*pdあります。たとえば、コンパイラがすべての D 型オブジェクトが 2 バイト境界で始まり、すべての B 型オブジェクトが 4 バイト境界で始まると想定している場合 (トリッキーで病的ですが、問題の可能性があります) )。
  • に vtable がある場合D、ポインタ変換によって初期化されません。したがって、クラスstatic_cast<D*>(pb)に導入された の仮想メソッドを呼び出すことはお勧めできません。D
  • RTTI は、RTT 情報をオブジェクトと一緒に保存することで実装できるため、問題を引き起こす可能性があります。RTTI は例外および で使用できるdynamic_castsため、影響を受けるのはそれだけtypeidではありません。
  • C++ 標準によると、この変換は未定義の動作につながります。つまり、問題のない一部のコンパイラ/C++ 実装でのBサブオブジェクトを使用できたとしても、すべてのコンパイラ/バージョンで動作するという保証はありません。*pd上記の点は私の頭に浮かんだ問題の一部ですが、私は決して専門家ではありません。基本的に、この変換を実行して結果のサブオブジェクトを使用したときに何が起こるかを予測する方法や保証する方法はありませんBpd

最後のコメント:

  • static_castこれに関する参照: 5.2.9/2、最後の 2 つの文
  • 私の答えは、最初の方法を「推奨」するという点で、田代昴の答えとは異なります。あなたはそれを書き留めることができ、それは整形式のプログラムです (つまり、コンパイルする必要があります)。 )。あなたが本当にこれをやりたいのなら、reinterpret_castあなたが何をしているのかを知っていること、そしてそれがハックであることを示すために、この周りにたくさんのコメントを使用することをお勧めします.
于 2013-03-28T19:03:47.623 に答える
0

方法 1:static_castを使用して基本クラスを使用して派生クラスを初期化できますが、これは不完全な派生オブジェクトになります 。

ソース

static_cast は、派生クラスからそのベースへの変換だけでなく、基本クラスからその派生クラスへの変換も、関連するクラスへのポインター間の変換を実行できます。これにより、適切なオブジェクトが変換された場合に少なくともクラスに互換性があることが保証されますが、実行時に、変換されるオブジェクトが実際に変換先の型の完全なオブジェクトであるかどうかを確認するための安全性チェックは実行されません。したがって、変換が安全であることを確認するのはプログラマの責任です。

次の例を参照してください。

ここでは、「bInt」というパブリック変数を持つ基本クラスを記述します。

class B {
public:
  int bInt;
};

ここで、「dInt」という独自の変数を持つサブクラスを作成します

class D: public B {
public:
  int dInt;
};

ここに、基本クラスの新しいインスタンスがあります。ベース オブジェクトの変数を初期化します。

B * baseObj = new B;
baseObj->bInt = 1;

ここでは、基本クラスを使用して、 を使用して派生クラスを宣言および初期化しstatic_castます。この時点で、*derivedObjは不完全であることに注意してください。具体的にderivedObj->dIntは、未定義の値があります。

D * derivedObj = static_cast<D*>(baseObj);

D は B から派生しているため、bInt変数もあります。また、以前static_castは を初期化していたため*derivedObj、 の値もの値bIntと同じであり、したがってこれらは等しいです。*baseObjbInt

if(baseObj->bInt == derivedObj->bInt)
{
  display("it's equal");
}

ただし、基本クラスにはdInt変数がないため、この変数は初期化されないままになり、この手順の結果は未定義になります。

int myNum = derivedObj->dInt;

static_cast を使用する場合は、派生クラスのメンバーを必ず初期化してください。とにかくそうするのは良い習慣です。

static_cast を使用する場合、B のすべてのメンバーを知る必要はありません。D は自動的に B のすべてのメンバー値を持ちます。しかし、不完全なオブジェクトを作成しているため、あなたがやろうとしていることは危険です。

方法 2:本当に B をサブクラス化する必要がある場合は、D のコンストラクターを B* を受け取り、次のようにコピーして B を初期化するコンストラクターとして記述します。

D(const B &pass) : B(pass)

したがって、型 D のオブジェクトを宣言して初期化する場合は、次のようにします。

B *baseObj = new B;
D *derivedObj = new D(*baseObj);

要約すると、次の 2 つの選択肢があります。

  1. B をサブクラス化し、static_cast を使用して D を初期化します。D のメンバー変数を必ず初期化してください。
  2. B をサブクラス化し、B を受け取り、それを B のコピー コンストラクターに渡すコンストラクターを記述します。

これらの方法はどちらも同じ結果になりますが、違いは、プログラムが舞台裏でこれを行う方法です。

残念ながら、あなたの例でd=bは、基本クラスを派生クラスに割り当てようとしているため、使用は「違法」(?) です。b=d代入演算子は、初期化された givenなど、逆にのみ使用できdます。

独自の代入演算子を作成してそれを行うこともできますが、個人的にはお勧めしません。

于 2013-03-26T18:34:29.630 に答える