代码之家  ›  专栏  ›  技术社区  ›  BCS

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

  •  5
  • BCS  · 技术社区  · 15 年前

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

    我现在的代码是:

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

    对于我来说,这是可行的。但是,这种情况不适用:

    class B1 {};
    class B2 {};
    class C : public B1, public B2 {}
    
    C c;
    B1 *a = &c;
    B2 *b = &c;
    

    分插 reinterpert_cast , static_cast dynamic_cast 也不起作用。


    尤其是我希望能得到一些真正简单有效的东西。理想情况下,它不需要任何分支指令来实现,并且可以执行类似的操作,调整指向对象开头的指针并进行比较。

    9 回复  |  直到 15 年前
        1
  •  8
  •   please delete me    15 年前

    如果您的类确实与给定的完全相同,那么就不可能了,因为在运行时没有足够的信息来重建所需的信息。

    如果它们实际上是具有虚拟函数的多态类,听起来就像 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 问题是适用的——也就是说,不能保证它是快速的,而且你的类必须是多态的。

    恐怕这不是我自己使用过的一个功能,但我经常看到有人建议它,我推断它得到了广泛的支持,并且可以像广告中那样工作。

        2
  •  3
  •   Pavel Minaev    15 年前

    没有一般的方法。一般来说,基类子对象不知道它们是什么,所以如果您只有一个指向基类子对象的指针,那么如果您事先不知道后者的类型,您就没有任何方法来获取指向它所属的最派生对象的指针。

    让我们从这个开始:

     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 从指针值本身看,显然没有办法判断;您(或编译器)也无法从指针引用的数据中推断出任何东西,因为它只是结构中的一个单字节数据。

        3
  •  3
  •   Potatoswatter    15 年前

    有一个简单的方法和一个困难的方法。

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

    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 );
    }
    

    当然,您不想将空指针传递给这个“优化”版本。

        4
  •  1
  •   sbi    15 年前

    除了智能指针(实际上不是指针,而是类对象)之外,重载 operator== 对于指针来说是不可能的,所以不需要演员表。

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

        5
  •  0
  •   Managu    15 年前

    所以,您需要一个编译时解决方案。我不相信这是可能的,正如C++中所说的那样。下面是一个思想实验:

    文件Bases.hpp:

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

    文件派生.hpp:

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

    文件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 C class 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))
    {
        ...
    }
    
        6
  •  0
  •   BCS    15 年前

    使用 boost::addressof . 我认为这是最好的方法。 boost::地址 提供以获取地址,不管运算符重载的潜在用途和误用。通过使用一些巧妙的内部机制,模板函数 addressof 确保它到达实际对象及其地址。看看这个

    #include "boost/utility.hpp"
    
    class some_class {};
    
    int main() {
      some_class s;
      some_class* p=boost::addressof(s);
    }
    
        7
  •  0
  •   xtofl Adam Rosenfield    15 年前

    如果需要比较对象的身份,为什么不给它们一个呢?毕竟,这是 谁来决定物体的身份。让编译器来做这件事,你就会受到编译器的限制。

    在某种程度上…

    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 ) );
    
        8
  •  -1
  •   Tarnschaf    15 年前

    你的方法对我有效,即使在你引用的案例中:

    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!");
     }
    
    }
    

    此处打印“等号”。

        9
  •  -2
  •   Managu    15 年前

    您可以检查指向内存中是否重叠的对象(从MarkRansom的答案中窃取)。这将调用未定义的行为,但应在任何合理的编译器上执行所需的操作:

    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);
    }