提问帖子地址:http://topic.csdn.net/u/20110809/23/886f4f2c-51e5-445e-a2d9-e3496963805f.html
本来是别人问了我一个小题目,自己没想出来,于是发到论坛上向大家求教,结果意外地收获到了很多东西,这里把一些精彩的回复贴过来。
提问:
有如下代码:
class A上面是两个类class A和class B,还有两个全局函数:callf(&A)和callg(&B)。分别在这两个全局函数中调用f()和g(),要求实现一个类C,分别用public和protected继承A和B。然后实例化C,在主函数中以C的实例作为参数,调用两个全局函数。这样能否通过编译?如果不能, 不改变程序的继承结构和全局函数, 修改程序, 实现要求。
{
public:
void f(){cout << "From Class A: SHIT!" << endl;}
};
class B
{
public:
void g(){cout << "From Class B:" << endl;}
};
void callf(A&a) {a.f();}
void callg(B&b) {b.g();}
按照要求,对于第一问,可以写出如下代码:
#include<iostream>这个代码编译自然是通不过,在callg(c)这个地方,保护继承的话,派生类对象肯定没法调用基类的方法了。那么要怎么改呢?先谢谢了~
using namespace std;
class A
{
public:
void f(){cout << "From Class A: SHIT!" << endl;}
};
class B
{
public:
void g(){cout << "From Class B:" << endl;}
};
void callf(A&a) {a.f();}
void callg(B&b) {b.g();}
class C:public A, protected B{};
int main()
{
C c;
callf(c);
callg(c);
return 0;
}
———————————————————6楼———————————————————————
下面这样就可以满足楼主的设想:
#include<iostream>思路是这样的:
using namespace std;
class A
{
public:
void f(){cout << "From Class A: SHIT!" << endl;}
};
class B
{
public:
virtual void g() // 改为virtual
{
cout << "From Class B:" << endl;
}
};
void callf(A* a) {a->f();} // 改用指针
void callg(B* b) {b->g();} // 改用指针
class C:public A, protected B
{
};
int main()
{
C* c = new C;
callf(c);
callg(reinterpret_cast<B*>(c)); // 这个地方要进行强制转换
return 0;
}
1. 将B中的函数改成虚函数,那么在C中就会产生一个虚函数表指针。C的内存布局大致是这样的:
+--------------------- Class C
| +------------------- Class B
| | {vfptr}
| +-------------------
| +------------------- Class A
| |
| +-------------------
+-----------------------
2. 从上图中可以看出B这个Suboject在C对象中的开始处,也就是C对象的地址和其中的B这个Suboject的地址是一样的,因此只要将C对象的地址,转换成B类型的地址即可。
3. 但正常的转型会报错,因为比如dynamic_cast转型的时候会进行语法检查,它会认为将C*转型为B*是不可以的(因为C是protected继承B的)。但我们很清楚地知道自己要干什么,因此,我们可以用reinterpret_cast进行强制转换,这是没有什么问题的。
4. 转换成功后,就可以随意处理了。使用类似的方法,在外面,调用一个类中的私有成员变量都是可以做到的。
———————————————————8楼———————————————————————
用reinterpret_cast的话,太暴力了点。
如果reinterpret_cast也算,又何必改那么多?只需要改一句就行了。把main函数中的:
callg(c);
这句改成:
callg(reinterpret_cast<B&>(c));
其它都不用动。
———————————————————11楼———————————————————————
哦哦哦。
另外刚刚我也想了一下,既然用上了reinterpret_cast,恐怕用虚函数也不见得安全,比如当函数中访问对象成员的时候。
当然,楼主的两个函数实现在多数编译器上问题不大,因为只是调用全局的cout打印两行语句。
———————————————————12楼———————————————————————
另外,友元可以的。不要friend callg,应该friend main。
———————————————————14楼———————————————————————
之所以说不安全,是指这样的用法本身就有可能不安全(尤其当B::g()实现较为复杂的时候),而不是说用完了之后怎样怎样。
因为从概念上讲,类型的隐式转换发生在main函数内部向callg传递参数的时候,而不是发生在callg函数“内部”。于是,需要了解“C从B派生”这一小秘密的是main,而不是callg。
———————————————————41楼———————————————————————
以下程序在vs2010,vs2008,devc++,下测试,均可正常通过编译.
#include<iostream>
using namespace std;
class A
{
public:
void f(){cout << "From Class A: SHIT!" << endl;}
};
class B
{
public:
void g(){cout << "From Class B:" << endl;}
};
void callf(A&a) {a.f();}
void callg(B&b) {b.g();}
class C:public A, protected B
{
friend void callg(C& c) //重载并声明为友元
{
callg(static_cast<B&>(c));
}
};
int main()
{
C c;
callf(c);
callg(c);
return 0;
}
———————————————————42楼———————————————————————
你对继承的理解不完全准确,而主要原因是把以下几件事情搞混了:
(1)子类对父类成员的访问权限跟如何继承没有任何关系,“子类可以访问父类的public和protected成员,不可以访问父类的private成员”——这句话对任何一种继承都是成立的。
(2)继承修饰符影响着谁可以知道“继承”这件事。public继承大家都知道,有点像“法定继承人”,因此,任何代码都可以把子类的引用(或指针)直接转换为父类。也因为这个原因,public继承常用来表达设计中所谓的“is-a”关系。private继承则有点像“私生子”,除了子类自己,没有人知道这层关系,也因此,除了子类自己的代码之外,没有其它人知道自己还有个父亲,于是也就没有其它人可以做相应的类型转换。为此,私有继承常用于表达非“is-a”的关系,这种情况下子类只是借用父类的某些实现细节。protected继承则有点特殊,外界同样不知道这层关系,但家族内部的子孙们可以知道,有点像“自家知道就行了,不许外扬”的意思,于是子孙们是可以做这种向上转型,其它代码则不可以。因为这种特殊性,protected继承在实际中用得很少。
(3)还需要补充一点,由于“继承关系”的可见性受到了影响,那么继承来的财产的可见性也必然受到影响。比如一个成员变量或成员函数,在父类中本来是public的,被某个子类protected继承之后,对子类来讲,这个成员就相当于protected成员了——继承是继承到了,但权限变了。具体的规则教材上都会讲的。
———————————————————43楼———————————————————————
你的代码确实没问题,我发现我自己的问题了。。我先前直接把重载的函数粘贴过去,结果放到了C类定义的前面——前面加上C类的声明,这样一来,函数出现在被声明为友元之前,导致使用static_cast的地方编译报错。将重载函数的定义放到C类定义之后,编译就通过了。
查了一下前面编译器提示的那个错误:
error C2440: 'static_cast' : cannot convert from 'class C' to 'class B &'
static_cast to reference can only be used for valid initializations or for lvalue casts between related classes
在MSDN中有对error C2440的解释: http://msdn.microsoft.com/en-us/library/sy5tsf8z(v=VS.100).aspx
关键的一点就是:Incompatible calling conventions for UDT return value message(UDT是指User Defined Type,即用户定义类型)
看了解释,我的理解是,前后的“调用约定(calling convention)”不一致导致错误,关于这个不是太明白,看了本地的中文版MSDN,大致感觉是:在前向声明中编译器假设使用C++调用约定,而等结构体被读完以后才发现默认是使用C调用约定,这样前后就不一致,在后面的代码中会出现问题。
具体还请参见MSDN上内容,我这里说的很不准确,也很模糊。
要解决由于不兼容的调用约定而产生的 C2440,MSDN给出了一个建议:To resolve C2440 because of incompatible calling conventions, declare functions that return a UDT after the UDT definition.
导致C2440的原因有多种,我觉得MSDN列出的这一个是最符合我的代码的情况。我理解为static_cast和C类使用的调用约定冲突了,不知道这样说对不对。
如果不是第一条,我觉得那也许是第三条:C2440 can also occur for an incorrect use of a user-defined conversion.
——————————————————————————————————————————
学到了东西,也发现了自己学习过程中的一些问题,真高兴~以后要更加努力和认真地去学习:)