protected继承,派生类对象如何访问基类成员?

时间:2022-09-07 22:17:41
有如下代码:
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 A和class B,还有两个全局函数:callf(&A)和callg(&B)。分别在这两个全局函数中调用f()和g(),要求实现一个类C,分别用public和protected继承A和B。然后实例化C,在主函数中以C的实例作为参数,调用两个全局函数。这样能否通过编译?如果不能, 不改变程序的继承结构和全局函数, 修改程序, 实现要求。

按照要求,对于第一问,可以写出如下代码:
#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{};

int main()
{
    C c;

    callf(c);
    callg(c);

    return 0;
}

这个代码编译自然是通不过,在callg(c)这个地方,保护继承的话,派生类对象肯定没法调用基类的方法了。那么要怎么改呢?先谢谢了~

61 个解决方案

#1


本帖最后由 steedhorse 于 2011-08-09 23:38:11 编辑
比如,把callg声明为C的友元?

#2


设为友元试试。

#3


这样?
void callf(A&a) {a.f();}
void callg(B&b) {b.g();}

class C:public A, protected B
{
friend void callg(B & b);
};

还是不行的。。。

#4


引用 1 楼 steedhorse 的回复:
比如,把callg声明为C的友元?


不过友元函数根本就不是成员函数,这和楼主的意图应该有所不同了。

#5


引用 3 楼 ptsntwsz 的回复:
这样?
C/C++ code
void callf(A&amp;a) {a.f();}
void callg(B&amp;b) {b.g();}

class C:public A, protected B
{
    friend void callg(B &amp; b);
};

还是不行的。。。

办法肯定有,不过应该是不太常规的做法。

#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. 转换成功后,就可以随意处理了。使用类似的方法,在外面,调用一个类中的私有成员变量都是可以做到的。

#7


引用 4 楼 pathuang68 的回复:
引用 1 楼 steedhorse 的回复:
比如,把callg声明为C的友元?


不过友元函数根本就不是成员函数,这和楼主的意图应该有所不同了。

确实,换成友元函数就和访问类成员对象不一致了。

引用 5 楼 pathuang68 的回复:
引用 3 楼 ptsntwsz 的回复:

这样?
C/C++ code
void callf(A&amp;amp;a) {a.f();}
void callg(B&amp;amp;b) {b.g();}

class C:public A, protected B
{
friend void callg(B &amp;amp; b);
};

还是不行的。。。

办……

不太常规的做法,可以给一个么~

引用 6 楼 pathuang68 的回复:
下面这样就可以满足楼主的设想:
C/C++ code

#include<iostream>
using namespace std;

class A
{
public:
    void f(){cout << "From Class A: SHIT!" << endl;}
};

class B
{
public:
    virtual void g()       // 改为vir……

讲得很详细,真是太谢谢了!这个代码编译可以通过,只是好像修改了全局函数,有点不符合题目要求。如果在全局函数里面还是按照原来那样,不改成指针,能不能做呢?

感谢大家的热心回答~

#8


用reinterpret_cast的话,太暴力了点。
如果reinterpret_cast也算,又何必改那么多?只需要改一句就行了。把main函数中的:
callg(c);
这句改成:
callg(reinterpret_cast<B&>(c));
其它都不用动。

#9


引用 8 楼 steedhorse 的回复:
用reinterpret_cast的话,太暴力了点。
如果reinterpret_cast也算,又何必改那么多?只需要改一句就行了。把main函数中的:
callg(c);
这句改成:
callg(reinterpret_cast<B&amp;>(c));
其它都不用动。

您说的对。最开始想整个虚函数好让楼主看得清楚点。

#10


按照8楼大佬的建议,修改如下,只改动了一个地方(注释处),道理还是和6楼给出的是一样的。

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

int main()
{
    C c;
    
    callf(c);
    callg(reinterpret_cast<B&>(c)); // 这个地方要进行强制转换

    return 0;
}

#11


引用 9 楼 pathuang68 的回复:
您说的对。最开始想整个虚函数好让楼主看得清楚点。

哦哦哦。
另外刚刚我也想了一下,既然用上了reinterpret_cast,恐怕用虚函数也不见得安全,比如当函数中访问对象成员的时候。
当然,楼主的两个函数实现在多数编译器上问题不大,因为只是调用全局的cout打印两行语句。

#12


另外,友元可以的。不要friend callg,应该friend main。

#13


引用 8 楼 steedhorse 的回复:
用reinterpret_cast的话,太暴力了点。
如果reinterpret_cast也算,又何必改那么多?只需要改一句就行了。把main函数中的:
callg(c);
这句改成:
callg(reinterpret_cast<B&amp;>(c));
其它都不用动。

看得真透彻!

引用 11 楼 steedhorse 的回复:
引用 9 楼 pathuang68 的回复:
您说的对。最开始想整个虚函数好让楼主看得清楚点。

哦哦哦。
另外刚刚我也想了一下,既然用上了reinterpret_cast,恐怕用虚函数也不见得安全,比如当函数中访问对象成员的时候。
当然,楼主的两个函数实现在多数编译器上问题不大,因为只是调用全局的cout打印两行语句。

不安全的话,如果用完以后再reinterpret_cast回去行不行呢?

引用 12 楼 steedhorse 的回复:
另外,友元可以的。不要friend callg,应该friend main。

确实,这样改就可以了。。。但是有点不太明白的,为什么friend callg不行而friend main可以呢?

感谢两位高手的热心解答,让我学到不少:)

#14


本帖最后由 steedhorse 于 2011-08-10 15:13:26 编辑
引用 13 楼 ptsntwsz 的回复:
不安全的话,如果用完以后再reinterpret_cast回去行不行呢?

之所以说不安全,是指这样的用法本身就有可能不安全(尤其当B::g()实现较为复杂的时候),而不是说用完了之后怎样怎样。

引用 13 楼 ptsntwsz 的回复:
但是有点不太明白的,为什么friend callg不行而friend main可以呢?

因为从概念上讲,类型的隐式转换发生在main函数内部向callg传递参数的时候,而不是发生在callg函数“内部”。于是,需要了解“C从B派生”这一小秘密的是main,而不是callg。

#15


ss A
{
public:
    void f(){cout << "From Class A: SHIT!" << endl;}
};

class B
{

#16


Good talk

#17


学习学习

#18


支持下

#19


不明白楼主为什么一定要protected继承而又需要在callg调用这个成员函数呢
有很多问题是由于不合理的设计造成的

#20


引用 14 楼 steedhorse 的回复:
之所以说不安全,是指这样的用法本身就有可能不安全(尤其当B::g()实现较为复杂的时候),而不是说用完了之后怎样怎样。

学习了!

引用 14 楼 steedhorse 的回复:
因为从概念上讲,类型的隐式转换发生在main函数内部向callg传递参数的时候,而不是发生在callg函数“内部”。于是,需要了解“C从B派生”这一小秘密的是main,而不是callg。

原来是这样!在main中进行类型转换,然后再传入到callg中去。
那么进行类型的隐式转换时,如果main不是C类的友元,则其不能使用C类从B类继承的方法,于是“不允许对不可访问的基类进行转换”——本来设计成protected继承就是为了不让派生类实例使用基类的成员,如果允许转换,那这个设计就没有用了。而当main成为C类的友元后,可以访问C类从B类继承的方法了,这个“友元”就是告诉编译器“我(main)要用到B类的方法”,让编译器确信coder知道自己在干什么,从而允许派生类对象转换为原本不可访问的基类。使用reinterpret_cast也是起到类似的作用吧。
将main作为B类的友元并没有用,因为在main中需要访问的是C类的实例而不是B类的实例。
这是个人对解释的一点理解,不知道有没有错误的地方,若有还请指正!

#21


引用 19 楼 xiaoqinguo 的回复:
不明白楼主为什么一定要protected继承而又需要在callg调用这个成员函数呢
有很多问题是由于不合理的设计造成的

这其实是别人问我的一道题目,一本C++书上的。。。呵呵,实际中应该不能这样做吧,这样做了就确实是个不好的设计。
单纯只是个题目,呵呵。不过也好,学到了很多东西~

#22


学习一下

#23


iostream.h

#24


也可以重载函数callg()

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

};
 void callg(C&c) {static_cast<B&>(c).g();}   //重载


int main()
{
    C c;
    
    callf(c);
    callg(c);

    return 0;
}


#25


太好了,学习中,谢谢

#26


该回复于2011-08-10 17:02:25被版主删除

#27


引用 24 楼 luuillu 的回复:
也可以重载函数callg()

上面的重载函数写错了,应该是:
 void callg(C&c)                    //重载
{
     callg(static_cast<B&>(c));
}   

#28


顶一个吧......呵呵

#29


学习一下

#30


学习一下学习一下学习一下

#31


学习一下学习一下学习一下

#32


引用 27 楼 luuillu 的回复:
引用 24 楼 luuillu 的回复:
也可以重载函数callg()

上面的重载函数写错了,应该是:
 void callg(C&amp;c)                    //重载
{
     callg(static_cast<B&amp;>(c));
}

这样重载也不行,编译还是不能过,提示和原来同样的错误。
不过这样重载之后,感觉设置callg为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
Error executing cl.exe.
看来用static_cast是不行的。改成reinterpret_cast以后可以了,不过这就回到前面最开始提到的方法了,呵呵。

#33


我想赚积分 是通过回帖的不

#34


引用 14 楼 steedhorse 的回复:
引用 13 楼 ptsntwsz 的回复:
不安全的话,如果用完以后再reinterpret_cast回去行不行呢?

之所以说不安全,是指这样的用法本身就有可能不安全(尤其当B::g()实现较为复杂的时候),而不是说用完了之后怎样怎样。


引用 13 楼 ptsntwsz 的回复:
但是有点不太明白的,为什么friend callg不行而friend main可以呢?

因为……

学习了,mark。

#35


顶……

#36


学习 学习 学习 学习 

#37


虽然不懂  但是支持

#38


引用 32 楼 ptsntwsz 的回复:
引用 27 楼 luuillu 的回复:

你确定把callg(C&)设为C的友元了?我在vs2010上测试可以通过编译啊,
我猜你用的编译器是vc++6.0,它有很多地方不符合c++标准。

#39


引用 38 楼 luuillu 的回复:
引用 32 楼 ptsntwsz 的回复:
引用 27 楼 luuillu 的回复:



你确定把callg(C&amp;)设为C的友元了?我在vs2010上测试可以通过编译啊,
我猜你用的编译器是vc++6.0,它有很多地方不符合c++标准。

vs2010也试了,只要是用static_cast就不行,即使两个全局函数callg(原来的和重载的)都设置为C类的友元,也不行,单独一个设置为友元,也不行。
能不能看看你的代码?

#40


强制转换太粗鲁了吧 友元多科学

#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


引用 20 楼 ptsntwsz 的回复:
原来是这样!在main中进行类型转换,然后再传入到callg中去。
那么进行类型的隐式转换时,如果main不是C类的友元,则其不能使用C类从B类继承的方法,于是“不允许对不可访问的基类进行转换”——本来设计成protected继承就是为了不让派生类实例使用基类的成员,如果允许转换,那这个设计就没有用了。而当main成为C类的友元后,可以访问C类从B类继承的方法了,这个“友元”就是告诉编译器“我(main)要用到B类的方法”,让编译器确信coder知道自己在干什么,从而允许派生类对象转换为原本不可访问的基类。使用reinterpret_cast也是起到类似的作用吧。
将main作为B类的友元并没有用,因为在main中需要访问的是C类的实例而不是B类的实例。
这是个人对解释的一点理解,不知道有没有错误的地方,若有还请指正!

你对继承的理解不完全准确,而主要原因是把以下几件事情搞混了:
(1)子类对父类成员的访问权限跟如何继承没有任何关系,“子类可以访问父类的public和protected成员,不可以访问父类的private成员”——这句话对任何一种继承都是成立的。
(2)继承修饰符影响着谁可以知道“继承”这件事。public继承大家都知道,有点像“法定继承人”,因此,任何代码都可以把子类的引用(或指针)直接转换为父类。也因为这个原因,public继承常用来表达设计中所谓的“is-a”关系。private继承则有点像“私生子”,除了子类自己,没有人知道这层关系,也因此,除了子类自己的代码之外,没有其它人知道自己还有个父亲,于是也就没有其它人可以做相应的类型转换。为此,私有继承常用于表达非“is-a”的关系,这种情况下子类只是借用父类的某些实现细节。protected继承则有点特殊,外界同样不知道这层关系,但家族内部的子孙们可以知道,有点像“自家知道就行了,不许外扬”的意思,于是子孙们是可以做这种向上转型,其它代码则不可以。因为这种特殊性,protected继承在实际中用得很少。
(3)还需要补充一点,由于“继承关系”的可见性受到了影响,那么继承来的财产的可见性也必然受到影响。比如一个成员变量或成员函数,在父类中本来是public的,被某个子类protected继承之后,对子类来讲,这个成员就相当于protected成员了——继承是继承到了,但权限变了。具体的规则教材上都会讲的。

#43


引用 41 楼 luuillu 的回复:
以下程序在vs2010,vs2008,devc++,下测试,均可正常通过编译.

C/C++ code

#include<iostream>
using namespace std;

class A
{
public:
    void f(){cout << "From Class A: SHIT!" << endl;}
};

class B
{
public:
    void ……

你的代码确实没问题,我发现我自己的问题了。。我先前直接把重载的函数粘贴过去,结果放到了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. 

引用 42 楼 steedhorse 的回复:
引用 20 楼 ptsntwsz 的回复:
原来是这样!在main中进行类型转换,然后再传入到callg中去。
那么进行类型的隐式转换时,如果main不是C类的友元,则其不能使用C类从B类继承的方法,于是“不允许对不可访问的基类进行转换”——本来设计成protected继承就是为了不让派生类实例使用基类的成员,如果允许转换,那这个设计就没有用了。而当main成为C类的友元后,可以访问C类从B……

看了朋友的解释,真是有点恍然大悟的感觉,解释得太好了!让我对于继承又有了更好的理解。

感谢大家的指点!

#44


mark

#45


非常不错。

#46


占楼学习

#47


好像上面代码都要转换哈~  实际上加个重载函数就可以优雅的解决了:

#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&); // 重载callg
};

void callg(C &c) {
callg(B(c)); // 指定调用 callg(B&)
}

int main() {
    C c;
    callf(c);
    callg(c);
    return 0;
}

#48


学习了。。。。

#49


顶一个。。。。。。。。。

#50


引用 47 楼 lisunlin0 的回复:
好像上面代码都要转换哈~ 实际上加个重载函数就可以优雅的解决了:

C/C++ code

#include<iostream>
using namespace std;

class A {
public:
    void f(){cout << "From Class A: SHIT!" << endl;}
};
class B {
public:
    void……

恩,重载的callg作为C类的友元,允许C类到B类的转换,在main函数中调用的是重载的函数。
就像中间搭了一座桥,是吧,呵呵~

#1


本帖最后由 steedhorse 于 2011-08-09 23:38:11 编辑
比如,把callg声明为C的友元?

#2


设为友元试试。

#3


这样?
void callf(A&a) {a.f();}
void callg(B&b) {b.g();}

class C:public A, protected B
{
friend void callg(B & b);
};

还是不行的。。。

#4


引用 1 楼 steedhorse 的回复:
比如,把callg声明为C的友元?


不过友元函数根本就不是成员函数,这和楼主的意图应该有所不同了。

#5


引用 3 楼 ptsntwsz 的回复:
这样?
C/C++ code
void callf(A&amp;a) {a.f();}
void callg(B&amp;b) {b.g();}

class C:public A, protected B
{
    friend void callg(B &amp; b);
};

还是不行的。。。

办法肯定有,不过应该是不太常规的做法。

#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. 转换成功后,就可以随意处理了。使用类似的方法,在外面,调用一个类中的私有成员变量都是可以做到的。

#7


引用 4 楼 pathuang68 的回复:
引用 1 楼 steedhorse 的回复:
比如,把callg声明为C的友元?


不过友元函数根本就不是成员函数,这和楼主的意图应该有所不同了。

确实,换成友元函数就和访问类成员对象不一致了。

引用 5 楼 pathuang68 的回复:
引用 3 楼 ptsntwsz 的回复:

这样?
C/C++ code
void callf(A&amp;amp;a) {a.f();}
void callg(B&amp;amp;b) {b.g();}

class C:public A, protected B
{
friend void callg(B &amp;amp; b);
};

还是不行的。。。

办……

不太常规的做法,可以给一个么~

引用 6 楼 pathuang68 的回复:
下面这样就可以满足楼主的设想:
C/C++ code

#include<iostream>
using namespace std;

class A
{
public:
    void f(){cout << "From Class A: SHIT!" << endl;}
};

class B
{
public:
    virtual void g()       // 改为vir……

讲得很详细,真是太谢谢了!这个代码编译可以通过,只是好像修改了全局函数,有点不符合题目要求。如果在全局函数里面还是按照原来那样,不改成指针,能不能做呢?

感谢大家的热心回答~

#8


用reinterpret_cast的话,太暴力了点。
如果reinterpret_cast也算,又何必改那么多?只需要改一句就行了。把main函数中的:
callg(c);
这句改成:
callg(reinterpret_cast<B&>(c));
其它都不用动。

#9


引用 8 楼 steedhorse 的回复:
用reinterpret_cast的话,太暴力了点。
如果reinterpret_cast也算,又何必改那么多?只需要改一句就行了。把main函数中的:
callg(c);
这句改成:
callg(reinterpret_cast<B&amp;>(c));
其它都不用动。

您说的对。最开始想整个虚函数好让楼主看得清楚点。

#10


按照8楼大佬的建议,修改如下,只改动了一个地方(注释处),道理还是和6楼给出的是一样的。

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

int main()
{
    C c;
    
    callf(c);
    callg(reinterpret_cast<B&>(c)); // 这个地方要进行强制转换

    return 0;
}

#11


引用 9 楼 pathuang68 的回复:
您说的对。最开始想整个虚函数好让楼主看得清楚点。

哦哦哦。
另外刚刚我也想了一下,既然用上了reinterpret_cast,恐怕用虚函数也不见得安全,比如当函数中访问对象成员的时候。
当然,楼主的两个函数实现在多数编译器上问题不大,因为只是调用全局的cout打印两行语句。

#12


另外,友元可以的。不要friend callg,应该friend main。

#13


引用 8 楼 steedhorse 的回复:
用reinterpret_cast的话,太暴力了点。
如果reinterpret_cast也算,又何必改那么多?只需要改一句就行了。把main函数中的:
callg(c);
这句改成:
callg(reinterpret_cast<B&amp;>(c));
其它都不用动。

看得真透彻!

引用 11 楼 steedhorse 的回复:
引用 9 楼 pathuang68 的回复:
您说的对。最开始想整个虚函数好让楼主看得清楚点。

哦哦哦。
另外刚刚我也想了一下,既然用上了reinterpret_cast,恐怕用虚函数也不见得安全,比如当函数中访问对象成员的时候。
当然,楼主的两个函数实现在多数编译器上问题不大,因为只是调用全局的cout打印两行语句。

不安全的话,如果用完以后再reinterpret_cast回去行不行呢?

引用 12 楼 steedhorse 的回复:
另外,友元可以的。不要friend callg,应该friend main。

确实,这样改就可以了。。。但是有点不太明白的,为什么friend callg不行而friend main可以呢?

感谢两位高手的热心解答,让我学到不少:)

#14


本帖最后由 steedhorse 于 2011-08-10 15:13:26 编辑
引用 13 楼 ptsntwsz 的回复:
不安全的话,如果用完以后再reinterpret_cast回去行不行呢?

之所以说不安全,是指这样的用法本身就有可能不安全(尤其当B::g()实现较为复杂的时候),而不是说用完了之后怎样怎样。

引用 13 楼 ptsntwsz 的回复:
但是有点不太明白的,为什么friend callg不行而friend main可以呢?

因为从概念上讲,类型的隐式转换发生在main函数内部向callg传递参数的时候,而不是发生在callg函数“内部”。于是,需要了解“C从B派生”这一小秘密的是main,而不是callg。

#15


ss A
{
public:
    void f(){cout << "From Class A: SHIT!" << endl;}
};

class B
{

#16


Good talk

#17


学习学习

#18


支持下

#19


不明白楼主为什么一定要protected继承而又需要在callg调用这个成员函数呢
有很多问题是由于不合理的设计造成的

#20


引用 14 楼 steedhorse 的回复:
之所以说不安全,是指这样的用法本身就有可能不安全(尤其当B::g()实现较为复杂的时候),而不是说用完了之后怎样怎样。

学习了!

引用 14 楼 steedhorse 的回复:
因为从概念上讲,类型的隐式转换发生在main函数内部向callg传递参数的时候,而不是发生在callg函数“内部”。于是,需要了解“C从B派生”这一小秘密的是main,而不是callg。

原来是这样!在main中进行类型转换,然后再传入到callg中去。
那么进行类型的隐式转换时,如果main不是C类的友元,则其不能使用C类从B类继承的方法,于是“不允许对不可访问的基类进行转换”——本来设计成protected继承就是为了不让派生类实例使用基类的成员,如果允许转换,那这个设计就没有用了。而当main成为C类的友元后,可以访问C类从B类继承的方法了,这个“友元”就是告诉编译器“我(main)要用到B类的方法”,让编译器确信coder知道自己在干什么,从而允许派生类对象转换为原本不可访问的基类。使用reinterpret_cast也是起到类似的作用吧。
将main作为B类的友元并没有用,因为在main中需要访问的是C类的实例而不是B类的实例。
这是个人对解释的一点理解,不知道有没有错误的地方,若有还请指正!

#21


引用 19 楼 xiaoqinguo 的回复:
不明白楼主为什么一定要protected继承而又需要在callg调用这个成员函数呢
有很多问题是由于不合理的设计造成的

这其实是别人问我的一道题目,一本C++书上的。。。呵呵,实际中应该不能这样做吧,这样做了就确实是个不好的设计。
单纯只是个题目,呵呵。不过也好,学到了很多东西~

#22


学习一下

#23


iostream.h

#24


也可以重载函数callg()

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

};
 void callg(C&c) {static_cast<B&>(c).g();}   //重载


int main()
{
    C c;
    
    callf(c);
    callg(c);

    return 0;
}


#25


太好了,学习中,谢谢

#26


该回复于2011-08-10 17:02:25被版主删除

#27


引用 24 楼 luuillu 的回复:
也可以重载函数callg()

上面的重载函数写错了,应该是:
 void callg(C&c)                    //重载
{
     callg(static_cast<B&>(c));
}   

#28


顶一个吧......呵呵

#29


学习一下

#30


学习一下学习一下学习一下

#31


学习一下学习一下学习一下

#32


引用 27 楼 luuillu 的回复:
引用 24 楼 luuillu 的回复:
也可以重载函数callg()

上面的重载函数写错了,应该是:
 void callg(C&amp;c)                    //重载
{
     callg(static_cast<B&amp;>(c));
}

这样重载也不行,编译还是不能过,提示和原来同样的错误。
不过这样重载之后,感觉设置callg为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
Error executing cl.exe.
看来用static_cast是不行的。改成reinterpret_cast以后可以了,不过这就回到前面最开始提到的方法了,呵呵。

#33


我想赚积分 是通过回帖的不

#34


引用 14 楼 steedhorse 的回复:
引用 13 楼 ptsntwsz 的回复:
不安全的话,如果用完以后再reinterpret_cast回去行不行呢?

之所以说不安全,是指这样的用法本身就有可能不安全(尤其当B::g()实现较为复杂的时候),而不是说用完了之后怎样怎样。


引用 13 楼 ptsntwsz 的回复:
但是有点不太明白的,为什么friend callg不行而friend main可以呢?

因为……

学习了,mark。

#35


顶……

#36


学习 学习 学习 学习 

#37


虽然不懂  但是支持

#38


引用 32 楼 ptsntwsz 的回复:
引用 27 楼 luuillu 的回复:

你确定把callg(C&)设为C的友元了?我在vs2010上测试可以通过编译啊,
我猜你用的编译器是vc++6.0,它有很多地方不符合c++标准。

#39


引用 38 楼 luuillu 的回复:
引用 32 楼 ptsntwsz 的回复:
引用 27 楼 luuillu 的回复:



你确定把callg(C&amp;)设为C的友元了?我在vs2010上测试可以通过编译啊,
我猜你用的编译器是vc++6.0,它有很多地方不符合c++标准。

vs2010也试了,只要是用static_cast就不行,即使两个全局函数callg(原来的和重载的)都设置为C类的友元,也不行,单独一个设置为友元,也不行。
能不能看看你的代码?

#40


强制转换太粗鲁了吧 友元多科学

#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


引用 20 楼 ptsntwsz 的回复:
原来是这样!在main中进行类型转换,然后再传入到callg中去。
那么进行类型的隐式转换时,如果main不是C类的友元,则其不能使用C类从B类继承的方法,于是“不允许对不可访问的基类进行转换”——本来设计成protected继承就是为了不让派生类实例使用基类的成员,如果允许转换,那这个设计就没有用了。而当main成为C类的友元后,可以访问C类从B类继承的方法了,这个“友元”就是告诉编译器“我(main)要用到B类的方法”,让编译器确信coder知道自己在干什么,从而允许派生类对象转换为原本不可访问的基类。使用reinterpret_cast也是起到类似的作用吧。
将main作为B类的友元并没有用,因为在main中需要访问的是C类的实例而不是B类的实例。
这是个人对解释的一点理解,不知道有没有错误的地方,若有还请指正!

你对继承的理解不完全准确,而主要原因是把以下几件事情搞混了:
(1)子类对父类成员的访问权限跟如何继承没有任何关系,“子类可以访问父类的public和protected成员,不可以访问父类的private成员”——这句话对任何一种继承都是成立的。
(2)继承修饰符影响着谁可以知道“继承”这件事。public继承大家都知道,有点像“法定继承人”,因此,任何代码都可以把子类的引用(或指针)直接转换为父类。也因为这个原因,public继承常用来表达设计中所谓的“is-a”关系。private继承则有点像“私生子”,除了子类自己,没有人知道这层关系,也因此,除了子类自己的代码之外,没有其它人知道自己还有个父亲,于是也就没有其它人可以做相应的类型转换。为此,私有继承常用于表达非“is-a”的关系,这种情况下子类只是借用父类的某些实现细节。protected继承则有点特殊,外界同样不知道这层关系,但家族内部的子孙们可以知道,有点像“自家知道就行了,不许外扬”的意思,于是子孙们是可以做这种向上转型,其它代码则不可以。因为这种特殊性,protected继承在实际中用得很少。
(3)还需要补充一点,由于“继承关系”的可见性受到了影响,那么继承来的财产的可见性也必然受到影响。比如一个成员变量或成员函数,在父类中本来是public的,被某个子类protected继承之后,对子类来讲,这个成员就相当于protected成员了——继承是继承到了,但权限变了。具体的规则教材上都会讲的。

#43


引用 41 楼 luuillu 的回复:
以下程序在vs2010,vs2008,devc++,下测试,均可正常通过编译.

C/C++ code

#include<iostream>
using namespace std;

class A
{
public:
    void f(){cout << "From Class A: SHIT!" << endl;}
};

class B
{
public:
    void ……

你的代码确实没问题,我发现我自己的问题了。。我先前直接把重载的函数粘贴过去,结果放到了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. 

引用 42 楼 steedhorse 的回复:
引用 20 楼 ptsntwsz 的回复:
原来是这样!在main中进行类型转换,然后再传入到callg中去。
那么进行类型的隐式转换时,如果main不是C类的友元,则其不能使用C类从B类继承的方法,于是“不允许对不可访问的基类进行转换”——本来设计成protected继承就是为了不让派生类实例使用基类的成员,如果允许转换,那这个设计就没有用了。而当main成为C类的友元后,可以访问C类从B……

看了朋友的解释,真是有点恍然大悟的感觉,解释得太好了!让我对于继承又有了更好的理解。

感谢大家的指点!

#44


mark

#45


非常不错。

#46


占楼学习

#47


好像上面代码都要转换哈~  实际上加个重载函数就可以优雅的解决了:

#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&); // 重载callg
};

void callg(C &c) {
callg(B(c)); // 指定调用 callg(B&)
}

int main() {
    C c;
    callf(c);
    callg(c);
    return 0;
}

#48


学习了。。。。

#49


顶一个。。。。。。。。。

#50


引用 47 楼 lisunlin0 的回复:
好像上面代码都要转换哈~ 实际上加个重载函数就可以优雅的解决了:

C/C++ code

#include<iostream>
using namespace std;

class A {
public:
    void f(){cout << "From Class A: SHIT!" << endl;}
};
class B {
public:
    void……

恩,重载的callg作为C类的友元,允许C类到B类的转换,在main函数中调用的是重载的函数。
就像中间搭了一座桥,是吧,呵呵~