18

Derived2つの基本クラスから直接継承するクラスがありBase1ますBase2Derived一般に、基本クラスへのポインターを比較して、それらが同じオブジェクトであるかどうかを判断するのが安全かどうかを知りたいです。

Base1* p1;
Base2* p2;

/*
 * Stuff happens here. p1 and p2 now point to valid objects of either their
 * base type or Derived
 */

//assert(p1 == p2); //This is illegal
assert(p1 == static_cast<Base1*>(p2)); //Is this ok?
assert(static_cast<Derived*>(p1) == static_cast<Derived*>(p2)); //How about this?

ポインタは有効であることが保証されていますが、必ずしもDerivedオブジェクトを指しているとは限りません。私の推測では、これはおそらく問題ないでしょうが、技術的なC++の観点からは問題がないかどうかを知りたいと思いました。私は実際にはポインタに対して操作を行うことはありません。ポインタが同じオブジェクトを指しているかどうかを知りたいだけです。

編集:私がそれを保証し、オブジェクトp1p2指すことができれば、それは安全のようです。Derrived基本的に、安全でない場合は安全かどうかを知りたいのですが、一方または両方がベースオブジェクトを指している場合、比較は必然的に失敗しますか?繰り返しますが、ポインタが有効であることを保証できます(つまり、オブジェクトp1を指すことはありません。Base2その逆も同様です) 。

4

7 に答える 7

6

Derived*比較する前にキャストするのが正しい方法です。

同様のトピックがあります:C++ポインターの多重継承の楽しみ

于 2012-06-28T21:36:52.007 に答える
6

まあ、いや、それは動作しません。

私は個人的に例による学習の大ファンなので、ここに1つあります。

#include <iostream>

class Base1
{
public:
    Base1()
    {
        numberBase1 = 1;
    }

    int numberBase1;
};

class Base2
{
public:
    Base2()
    {
        numberBase2 = 2;
    }

    int numberBase2;
};

class Derived : public Base1, public Base2
{
public:
    Derived()
    {
        numberDerived = 3;
    }

    int numberDerived;
};

int main()
{
    Derived d;
    Base1 *b1 = &d;
    Base2 *b2 = &d;

    std::cout << "d: " << &d << ", b1: " << b1 << ", b2: " << b2 << ", d.numberDerived: " << &(d.numberDerived) << std::endl;

    return 0;
}

私のコンピューターの1つのランスルーはこれを出力しました:

d: 0035F9FC, b1: 0035F9FC, b2: 0035FA00, d.numberDerived: 0035FA04

Soo .. dのアドレスを0と定義すると、b1は0、b2は+4、dの数は+8になります。これは、私のマシンのintが4バイト長であるためです。

基本的に、C++が内部的にクラスを表す方法のレイアウトを確認する必要があります。

Address:    Class:
0           Base1
4           Base2
8           Derived

..したがって、全体として、Derivedクラスをインスタンス化すると、派生クラスの基本クラスにスペースが割り当てられ、最終的に派生オブジェクト自体のためのスペースが確保されます。ここには3つの整数があるので、12バイトになります。

さて、あなたが求めているのは(私が何かを誤解していない限り)、異なる基本クラスのポインタのアドレスを互いに比較して、それらが同じオブジェクトを指しているかどうかを確認できるかどうかです。答えはノーです。少なくとも直接ではありません。私の例のように、b1は0035F9FCを指し、b2は0035FA00を指します。C ++では、このオフセットはすべてコンパイル時に行われます。

おそらく、RIIAとsizeof()を使用して魔法をかけ、オフセットb2のどれだけをb1と比較できるようにする必要があるかを判断できますが、仮想のような他のあらゆる種類の問題が発生します。要するに、私はこのアプローチをお勧めしません。

ialiashkevichが言ったように、はるかに優れた方法はDerived *にキャストすることですが、オブジェクトがDerived *のインスタンスでない場合は、問題が発生します。

(免責事項。私は3〜4年間C ++を使用していないので、ゲームから少し離れている可能性があります。優しくしてください:))

于 2012-06-28T21:53:02.430 に答える
2

さて、あなたが探しているものを達成するための最短の方法は次のとおりです。

assert(dynamic_cast<void*>(p1) == dynamic_cast<void*>(p2));

動的にキャストしvoid*て、指定されたポインタを最も派生したクラスに効果的にダウンキャストするため、両方が同じオブジェクトを指している場合でも、アサートは失敗しません。

確かに、ポインタを無効にするための動的キャストには実際的な使用法があります...

編集:質問の編集に答えるために、比較安全ではありません。次のコードを検討してください。

Base2 b2;
Base1 b1;
assert(static_cast<Derived*>(&b1) == static_cast<Derived*>(&b2));  // succeeds!

2つの異なるベースのメモリレイアウトは、のメモリレイアウトと似ていますDerived(一般的な実装では、スタックはヒープの反対側に大きくなります)。1static_castつ目はポインタをそのままにしますが、2つ目はポインタsizeof(Base1)を元に戻すため、両方ともを指し&b1、オブジェクトが異なっていてもアサートは成功します。

static_castキャストが正しいことが確実にわかっている場合にのみ使用してください。これはあなたのケースではないのでdynamic_cast、おそらく上記のように、を使用する必要があります。

于 2012-06-29T06:39:43.153 に答える
1

簡単な答えはノーです、これは一般的に良い考えではありません。

:これは、すべてのクラスにカスタムの同等性が必要であることを前提としています。それらが同じオブジェクトであるかどうかを確認する場合は、を実行することをお勧めします(Derived *)

はるかに優れた解決策は、、、およびの演算子をオーバーロードすること==です。Base1Base2Derived

平等のためのBase11つのパラメーターがあり、平等のための別のパラメーターがあると仮定します。param1Base2param2

virtual bool Base1::operator==(object& other){
    return false;
}

virtual bool Base1::operator==(Base1& other)
{
    return this.param1 == other.param1;
}

virtual bool Base2::operator==(object& other){
    return false;
}

virtual bool Base2::operator==(Base2& other)
{
    return this.param2 == other.param2;
}

virtual bool Derived::operator==(object& other){
    return false;
}

virtual bool Derived::operator==(Derived& other){
    return this.param1 == other.param1 && this.param2 == other.param2;
}

virtual bool Derived::operator==(Base1& other){
    return this.param1 == other.param1;
}

virtual bool Derived::operator==(Base2& other){
    return this.param2 == other.param2;
}
于 2012-06-28T21:26:39.643 に答える
0
assert(p1 == p2);                      //This is illegal
assert(p1 == static_cast<Base1*>(p2)); //Is this ok?
assert(static_cast<Derived*>(p1) 
       == static_cast<Derived*>(p2));  //How about this?

それらのどれも良い解決策ではありません。無関係なタイプのポインターを比較できないため、最初のものはコンパイルされません。2つ目も、同じ理由でコンパイルされません(継承によって関連付けられている場合を除きますBase1) 。つまり、無関係なタイプのポインターにリンクすることはできません。Base2static_cast

3番目のオプションはborderlineです。つまり、正しくありませんが、多くの場合(継承が仮想でない限り)機能します。

IDを比較する適切な方法はdynamic_cast、派生型を使用してnullをチェックすることです。

{
  Derived *tmp = dynamic_cast<Derived*>(p1);
  assert( tmp && tmp == dynamic_cast<Derived*>(p2) );
{
于 2012-06-28T22:34:25.513 に答える
0

このSOの質問に基づくと、無効の ようです。C++の多重継承はどのように実装されていますか?

基本的に、オブジェクトがメモリに配置される方法のために、またはのいずれかにキャストするとBase1*Base2*実行時に任意に反転できないポインタのミューテーションが発生しますdynamic_cast。これは避けたいと思います。みんな、ありがとう!

于 2012-06-28T21:51:02.300 に答える
0

を使用dynamic_castし、NULLに注意してください。

#include <cassert>

struct Base1 { virtual ~Base1() {} };
struct Base2 { virtual ~Base2() {} };
struct Derived : Base1, Base2 {};

bool IsEqual(Base1 *p1, Base2 *p2) {
  Derived *d1 = dynamic_cast<Derived*>(p1);
  Derived *d2 = dynamic_cast<Derived*>(p2);

  if( !d1 || !d2 ) return false;
  return d1 == d2;
}

int main () {
  Derived d;
  Base1 *p1 = &d;
  Base2 *p2 = &d;
  Base1 b1;
  Base2 b2;

  assert(IsEqual(p1, p2));
  assert(!IsEqual(p1, &b2));
  assert(!IsEqual(&b1, p2));
  assert(!IsEqual(&b1, &b2));
}
于 2012-06-28T21:57:09.850 に答える