应该用什么来检查C ++中的身份?

时间:2009-10-01 22:23:55

标签: c++ casting identity

我有两个指向对象的指针,我想以最健壮的方式测试它们是否是完全相同的对象。我明确地不想调用任何operator ==重载,并且无论使用什么基类,虚基类和多重继承,我都希望它能够工作。

我目前的代码是:

((void*)a) == ((void*)b)

对于我的情况,这是有效的。但是,这不适用于这种情况:

class B1 {};
class B2 {};
class C : public B1, public B2 {}

C c;
B1 *a = &c;
B2 *b = &c;

reinterpert_caststatic_castdynamic_cast中的子搜索也不起作用。


特别是我希望最终会变得非常简单和有效。理想情况下,它不需要任何分支指令来实现,并且会执行类似的操作,将指针调整到对象的开头并进行比较。

9 个答案:

答案 0 :(得分:8)

如果你的课程真的完全一样,那么这是不可能的,因为在运行时没有足够的信息来重建所需的信息。

如果它们实际上是具有虚函数的多态类,则听起来dynamic_cast<void *>就是答案。它返回指向派生程度最高的对象的指针。您的支票将为dynamic_cast<void *>(a)==dynamic_cast<void *>(b)

见第7段:

http://www.csci.csusb.edu/dick/c++std/cd2/expr.html#expr.dynamic.cast

我怀疑通常的dynamic_cast问题适用 - 即,不保证它会很快,而且你的课程必须是多态的。

这不是我自己使用过的一个功能,我担心 - 但我已经看到它经常被那些我认为广泛支持并且像宣传的那样工作的人所建议。

答案 1 :(得分:3)

没有一般的方法可以做到这一点。一般来说,基类子对象不知道它们是那样的,所以如果你只有一个指向基类子对象的指针,你就没有办法获得指向它所属的最派生对象的指针,如果你事先不知道后者的类型。

让我们从这开始:

 struct B1 { char b1; };
 struct B2 { char b2; };
 struct D : B1, B2 { char d; };

 // and some other type...
 struct Z : B2, B1 { };

考虑D的内存布局的典型实现。在没有vtable的情况下,我们唯一拥有的是原始数据(可能还有填充):

       Offset    Field
       ------    -----
    /       0    b1     >- B1
 D-<        1    b2     >- B2
    \       2    d  

你有两个指点:

B1* p1;
B2* p2;

每个指针实际指向char实例中的单个D。但是,如果你事先不知道,你怎么知道?指针也有可能指向Z实例中的子对象,并查看指针值本身,显然无法分辨;您(或编译器)也不能从指针引用的数据中推断出任何东西,因为它只是结构中的单个数据字节。

答案 2 :(得分:3)

有一种简单的方法和艰难的方式。

简单的方法是引入一个空的虚拟基类。从这样的类继承的每个对象都获得一个指向“真实”对象中公共点的指针,这就是你想要的。指针有一点开销,但没有分支或任何东西。

class V {};
class B1 : public virtual V {}; // sizeof(B1) = sizeof(void*)
class B2 : public virtual V {}; // sizeof(B2) = sizeof(void*)
class D : public B1, public B2 {}; // sizeof(D) = 2*sizeof(void*)

bool same( V const *l, V const *r ) { return l == r; }

困难的方法是尝试使用模板。这里有一些黑客攻击......当用模板进行黑客攻击时,记住你实际上是在重新发明部分语言,只是希望通过管理编译时信息来降低开销。我们可以降低虚拟基类的开销并消除指针吗?这取决于你需要多少普遍性。如果您的基类可以在派生对象中以多种不同的方式排列,那么肯定会有信息在编译时无法获得。

但是如果你的继承层次结构是一个颠倒的树(即,你是通过大量的多重继承来构建大型对象),或者是几个这样的树,你可以继续将指针强制转换为派生类型,如下所示:

class C; // forward declare most derived type
class T { public: typedef C base_t; }; // base class "knows" most derived type
class B1: public T { int a; };
class B2: public T { int b; };
class D: public B1, public B2 { int c; };

 // smart comparison function retrieves most-derived type
 // and performs simple addition to make base pointers comparable
 // (if that is not possible, it fails to compile)
template< class ta, class tb >
bool same( ta const *l, tb const *r ) {
        return static_cast< typename ta::base_t const * >( l )
         == static_cast< typename tb::base_t const * >( r );
}

当然,您不希望将NULL指针传递给此“优化”版本。

答案 3 :(得分:1)

除了智能指针(不是指针,而是类对象)之外,指针不能重载operator==,因此不需要强制转换。

当然,比较不同类型的指针可能不起作用。为什么你认为你需要这样做?

答案 4 :(得分:0)

所以,你正在寻找一个编译时解决方案。我不相信这是可能的,如C ++中所述。这是一个思想实验:

文件Bases.hpp:

class B1 {int val1;};
class B2 {int val2;};

File Derived.hpp:

#include <Bases.hpp>
class D : public B1, public B2 {};

File Composite.hpp:

#include <Bases.hpp>
class C
{
   B1 b1;
   B2 b2;
};

文件RandomReturn.cpp:

#include <Composite.hpp>
#include <Derived.hpp>
#include <cstdlib>

static D derived;
static C composite;

void random_return(B1*& left, B2*& right)
{
    if (std::rand() % 2 == 0)
    {
        left=static_cast<B1*>(&derived);
        right=static_cast<B2*>(&derived);
    }
    else
    {
        left=&composite.b1;
        right=&composite.b2;
    }
}

现在,假设你有:

#include <Bases.hpp>
#include <iostream>

extern void random_return(B1*& , B2*& );

// some conception of "is_same_object"    
template <...> bool is_same_object(...) ...

int main()
{
    B1 *left;
    B2 *right;

    random_return(left,right);
    std::cout<<is_the_same_object(left,right)<<std::endl;
}

我们怎么可能在编译时实现is_same_object,而不知道有关class Cclass D的任何内容?

另一方面,如果你愿意改变假设,它应该是可行的:

class base_of_everything {};
class B1 : public virtual base_of_everything {};
class B2 : public virtual base_of_everything {};

class D : public B1, public B2, public virtual base_of_everything {};

...
// check for same object
D d;
B1 *b1=static_cast<B1*>(&d);
B2 *b2=static_cast<B2*>(&d);

if (static_cast<base_of_everything*>(b1)==static_cast<base_of_everything*>(b2))
{
    ...
}

答案 5 :(得分:0)

使用boost::addressof。我认为这是最好的方式。无论如何,都会提供boost::addressof来获取地址,而不管操作符重载的潜在用途和误用。通过使用一些聪明的内部机制,模板函数addressof确保它到达实际对象及其地址。看看这个

#include "boost/utility.hpp"

class some_class {};

int main() {
  some_class s;
  some_class* p=boost::addressof(s);
}

答案 6 :(得分:0)

如果您需要比较对象的身份,为什么不给他们一个?毕竟,决定是什么使对象的身份。让编译器去做,你就受到了编译器的限制。

阻碍......

class identifiable {
    public:
    long long const /*or whatever type*/ identity;
    static long long sf_lFreeId() { 
       static long long lFreeId = 0;
       return lFreeId++; // not typesafe, yet
    }
    identifiable(): identity( sf_lFreeId() ) {}
    bool identical( const identifiable& other ) const { 
      return identity == other. identity;
    }
};

class A : public identifiable {
};

....

A a1, a2;
A& a3 = a1;

assert( !a1.identical(a2) );
assert( a1.identical( a3 ) );

答案 7 :(得分:-1)

即使在您引用的案例中,您的方法也适用于我:

class B1 {};
class B2 {};
class C : public B1, public B2 {};

int main() {
  C c;
  B1 *a = &c;
  B2 *b = &c;

 if ((void*)a == (void*)b) {
  printf("equal");
 }
 else {
  printf("not equal!");
 }

}

在这里打印“相等”..

答案 8 :(得分:-2)

您可以检查指示对象是否指向内存中的重叠(从Mark Ransom的回答中窃取)。这会调用未定义的行为,但应该在任何合理的编译器上执行您想要的操作:

template <typename T1, typename T2>
bool is(const T1 *left, const T2 * right)
{
    const char *left_begin=reinterpret_cast<const char*>(left);
    const char *left_end=left_begin+sizeof(*left);

    const char *right_begin=reinterpret_cast<const char*>(right);
    const char *right_end=right_begin+sizeof(*right);

    return ( left_begin  <= right_begin && right_begin < left_end) ||
           ( right_begin <= left_begin  && left_begin < right_end);
}