我有一个类Derived
,它直接从两个基类Base1
和Base2
继承。我想知道一般来说,将指针与基类进行比较以确定它们是否是相同的Derived
对象是否安全:
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 ++的角度来看它是否可以。我实际上从不对指针进行任何操作,我只是想知道它们是否指向同一个对象。
编辑:如果我可以保证p1
和p2
指向Derrived
个对象,那似乎是安全的。我基本上想知道如果它们不是 - 如果一个或两个都指向一个基础对象,它是否安全,那么比较是否会失败?同样,我可以保证指针有效(即,p1
永远不会指向Base2
个对象,反之亦然)
答案 0 :(得分:6)
嗯,不,它不起作用。
我个人非常喜欢逐个学习,所以这里有一个:
#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;
}
我的电脑上有一个输入输出:
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相当多少,但是你会遇到各种其他问题,比如虚拟。简而言之,我不推荐这种方法。
更好的方法是投射到Derived *,就像ialiashkevich所说的那样,如果您的对象不是Derived *的实例,则会产生问题。
(免责声明;我在3 - 4年内没有使用过C ++,所以我的游戏可能有些偏差。温柔:))
答案 1 :(得分:5)
在比较之前投射到Derived*
是正确的方法。
有一个类似的主题:C++ pointer multi-inheritance fun
答案 2 :(得分:1)
简短的回答是否定的,这通常不是一个好主意。
注意:这是假设您希望所有类的自定义等效,如果要检查它们是否是同一个对象,最好是(Derived *)
。
更好的解决方案是重载==
,Base1
和Base2
的{{1}}运算符。
假设Derived
有1个参数Base1
表示相等,param1
有另一个参数Base2
表示相等:
param2
答案 3 :(得分:1)
嗯,结果是找到你想要的最短路径:
assert(dynamic_cast<void*>(p1) == dynamic_cast<void*>(p2));
动态转换为void*
有效地将给定指针向下转换为其最派生类,因此如果两者都指向同一对象,则保证断言不会失败。
确实,there are practical uses for dynamic-casting to void pointer ......
编辑:要回答问题的编辑,比较不是安全。请考虑以下代码:
Base2 b2;
Base1 b1;
assert(static_cast<Derived*>(&b1) == static_cast<Derived*>(&b2)); // succeeds!
两个不同基数的内存布局类似于Derived
的内存布局(在常见实现上 - 堆栈与堆相反)。第一个static_cast
将指针保持原样,但第二个指针将指针sizeof(Base1)
移回,所以现在它们都指向&b1
,并且断言成功 - 即使对象不同
只有在您确定演员表是正确的情况下才应使用static_cast
。这不是您的情况,因此您必须使用dynamic_cast
,可能如上所述。
答案 4 :(得分:0)
基于此SO问题,它似乎无效: How is C++'s multiple inheritance implemented?
基本上,由于对象在内存中的布局方式,对Base1*
或Base2*
的强制转换会导致指针突变,我无法在运行时任意反转dynamic_cast
,我想避免。谢谢大家!
答案 5 :(得分: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));
}
答案 6 :(得分: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?
它们都不是好的解决方案。第一个不会编译,因为你无法比较不相关类型的指针。第二个也不会编译(除非Base1
和Base2
通过继承相关),原因相同:你不能static_cast
指向不相关类型的指针。
第三个选项是 borderline 。也就是说,它不正确,但它在许多情况下都会起作用(只要继承不是虚拟的)。
比较身份的正确方法是使用dynamic_cast
来派生类型并检查null:
{
Derived *tmp = dynamic_cast<Derived*>(p1);
assert( tmp && tmp == dynamic_cast<Derived*>(p2) );
{