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

发布于 2024-08-06 14:39:44 字数 529 浏览 2 评论 0原文

我有两个指向对象的指针,我想以最可靠的方式测试它们是否是完全相同的对象。我明确不想调用任何 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 中进行替补也不起作用。


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

I have two pointers to objects and I want to test if they are the exact same object in the most robust manner. I explicitly do not want to invoke any operator == overloads and I want it to work no matter what base classes, virtual base classes and multiple inheritance is used.

My current code is this:

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

And for my case this works. However, that doesn’t work for this case:

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

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

Subbing in reinterpert_cast, static_cast or dynamic_cast doesn't work either.


Particularly I'm hoping for something that ends up really simple and efficient. Ideally it wouldn't require any branch instructions to implement and would do something like, adjust the pointer to the start of the object and compare.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

原来分手还会想你 2024-08-13 14:39:45

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

有什么东西挡住了...

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

If you need to compare the identity of your objects, why wouldn't you give them one? After all, it's you who decides what makes the identity of the object. Let the compiler do it, and you're bound to the compiler's limitations.

Something in the way of...

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 ) );
大海や 2024-08-13 14:39:45

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

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

}

在这里打印“等于”..

Your approach worked for me even in your cited case:

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

}

prints "equal" here..

悍妇囚夫 2024-08-13 14:39:45

您可以检查对象是否指向内存中的重叠(从 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);
}

You could check to point if the objects pointed to overlap in memory (stealing from Mark Ransom's answer). This invokes undefined behavior, but should do what you want on any reasonable compiler:

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);
}
甜点 2024-08-13 14:39:44

如果您的类确实与给定的完全一样,那么这是不可能的,因为运行时没有足够的信息来重建所需的信息。

如果它们实际上是具有虚函数的多态类,那么听起来 dynamic_cast 就是答案。它返回一个指向最派生对象的指针。您的检查将是 dynamic_cast(a)==dynamic_cast(b)

请参阅此处第 7 段:

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

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

恐怕这不是我自己使用过的功能——但我经常看到拥有此功能的人建议它,我推断它得到了广泛的支持并且按照广告宣传的那样工作。

If your classes are genuinely exactly as given then it's impossible as there's not enough information available at runtime to reconstruct the required information.

If they're actually polymorphic classes, with virtual functions, it sounds like dynamic_cast<void *> is the answer. It returns a pointer to the most derived object. Your check would then be dynamic_cast<void *>(a)==dynamic_cast<void *>(b).

See paragraph 7 here:

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

I suspect the usual dynamic_cast issues apply -- i.e., no guarantee it will be quick, and your classes will have to be polymorphic.

This is not a feature I have used myself, I'm afraid -- but I have seen it suggested often enough by people who have that I infer it is widely-supported and works as advertised.

等待我真够勒 2024-08-13 14:39:44

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

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

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 指针传递给这个“优化”版本。

There's an easy way and a hard way.

The easy way is to introduce an empty virtual base class. Every object inheriting from such a class gets a pointer to the common point in the "real" object, which is what you want. The pointer has a little overhead but there are no branches or anything.

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

The hard way is to try to use templates. There are a few hacks here already… when hacking with templates remember that you are essentially reinventing part of the language, just with hopefully lower overhead by managing compile time information. Can we lower the overhead of the virtual base class and eliminate that pointer? It depends on how much generality you need. If your base classes can be arranged in several different ways within a derived object, then there is certainly information that you can't get at compile time.

But if your inheritance hierarchy is an upside-down tree (ie, you are building large objects by lots of multiple inheritance), or several such trees, you can just go ahead and cast the pointers to the most derived type like this:

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

Of course, you don't want to pass NULL pointers to this "optimized" version.

泅人 2024-08-13 14:39:44

没有通用的方法可以做到这一点。一般来说,基类子对象不知道它们是这样的,因此,如果您只有一个指向基类子对象的指针,则您没有任何方法来获取指向它所属的最派生对象的指针,如果您事先不知道后者的类型。

让我们从这里开始:

 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;

每个指针有效地指向 D< 实例中的单个 char /代码>。然而,如果你事先不知道,你怎么能知道呢?还有一种可能性是指针可能指向 Z 实例中的子对象,并且查看指针值本身,显然无法判断;您(或编译器)也无法从指针引用的数据中推断出任何内容,因为它只是结构中的单个字节数据。

There's no general way to do it. Base class subobjects, in general, have no knowledge that they are that, so if you only have a pointer to a base class subobject, you do not have any means to obtain a pointer to the most derived object to which it belongs, if you do not know the type of the latter in advance.

Let's start with this:

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

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

Consider a typical implementation of in-memory layout of D. In the absence of vtable, the only thing we have is the raw data (and possibly padding):

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

You have two pointers:

B1* p1;
B2* p2;

Each pointer effectively points at a single char within an instance of D. However, if you do not know that in advance, how could you tell? There's also a possibility that pointers could rather point to subobjects within an instance of Z, and looking at pointer values themselves, there's clearly no way to tell; nor is there anything you (or the compiler) can deduce from data referenced by the pointer, since it's just a single byte of data in the struct.

笑脸一如从前 2024-08-13 14:39:44

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

当然,比较不同类型的指针可能行不通。您认为为什么需要这样做?

Except for smart pointers (which aren't really pointers, but class objects), overloading operator== isn't possible for pointers, so a cast shouldn't be necessary.

Of course, comparing pointers of different types might not work. Why do you think you need to do that?

枫以 2024-08-13 14:39:44

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

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

File 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 ,不了解C 类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))
{
    ...
}

So, you're looking for a compile time solution. I don't believe this is possible as stated in C++. Here's a thought experiment:

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

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

Now, suppose you have:

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

How could we possibly implement is_same_object here, at compile time, without knowing anything about class C and class D?

On the other hand, if you're willing to change the hypotheses, it should be workable:

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))
{
    ...
}
黑白记忆 2024-08-13 14:39:44

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

#include "boost/utility.hpp"

class some_class {};

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

Use boost::addressof. I'm thinking this is the best way. boost::addressof is provided to get the address anyway, regardless of potential uses and misuses of operator overloading. By using some clever internal machinery, the template function addressof ensures that it gets to the actual object and its address. Look at this

#include "boost/utility.hpp"

class some_class {};

int main() {
  some_class s;
  some_class* p=boost::addressof(s);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文