damn multi-inheritance!
why?
1. diamond inheritance. B:public A; C:public A; D:public B, C;
Although C++ has virtual inheritance to solve this.
But it is only making things much more complex. Simply a mess.
He who can stand out and tell how virtual inheritance works, how the memory layout would be after virtual inheritance, please raise your hand!
2. performance problem.
class A: public B, C{};
void f(A* pa){C* pc = pa;}
guess what is the assembly code for the pc=pa?
pseudo code:
is pa 0?
if yes, move 0 to pc;
if no, offset pa to make it point to the object of C.
See what a simple assignment/initialization statement becomes?
Here, the cost is much more than a simple Mov instruction. the "if" statement can even invalidate the instruction prefetch!
Also, for virtual functions, the "this" pointer has to be moved up and down by the caller and callee.
3. inheritance itself should be avoided.
Why? Isn't that what OO is for?
No. Simply put, subtyping (related to interfaces) is what OO is really for.
Subclassing (classes), on the other hand, should be used in a very disciplined way.
Normally, aggregation should be used instead.
To discuss inheritance's cons and pros, it could get very long.
If who's interested in that, we can discuss in another topic.
问题点数:100、回复次数:71Top
1 楼sandwish2000(紫麒麟)回复于 2002-06-02 08:49:09 得分 0
just agree with you !
Top
2 楼ajoo(聪明的一猪)回复于 2002-06-04 06:10:46 得分 0
anyone else? no matter you are against me or not. As long as you share your thoughts with us, that's fine.
Just want to give out the 100 points. Too much available points!Top
3 楼huxw()回复于 2002-06-04 10:29:21 得分 0
菱形继承关系确实很糟糕,但这不是多继承的错。就好像你不能看到街上一个男人,就说他是淫棍一样;他虽然有作案工具,但是没有作案东西啊。 ;)
效率问题,我还是觉得不能避免。用了继承,就有酱紫的代价吧。
应该实现继承还是接口继承,我当然更喜欢前者。C++中这两者没有从语法上分开,失败。你说的聚合(aggregation)是不是类似mixin的概念啊?我不清楚,你解释一下先吧。 ;))
Top
4 楼kof99th(小虫)回复于 2002-06-04 10:45:56 得分 10
inside c++ object 上说virtual inheritance是通过一个vbtr来完成的,通过间接的寻址来完成,但是对于多重虚继承的具体实现各家编译器有各自的做法,基类对象在派生类中的位置不是在编译期决定,一切都要被推迟到执行期,所以没有效率.
我个人的理解,对于虚继承,编译器在背后作了太多的工作,让我们很难判断究竟发生了什么,比如通过子类取虚基类的成员时,会想到是通过指针去取的吗?还有,虚拟继承的类的构造,拷贝,赋值......,太麻烦了,如果虚基类中有成员,完全无法想象构造函数中发生了什么事,编译器居然会把你的构造函数加上额外的参数来支持它.(
详见<inside the c++ object modal>),太恐怖了.
顺便想一下,如果多继承和虚拟继承一起用会怎么样?
所以,书中给了一个建议,不要在虚基类中声明成员变量.Top
5 楼ajoo(聪明的一猪)回复于 2002-06-04 22:24:19 得分 0
"但这不是多继承的错"
then whose fault is it?
when you do multi-inheritance, do you need to go through all the base classes of the parents to make sure they are not relative? Even if so, how do you prevent the author of the base classes later refactor and introduce a common base class?
In short, it's not scalable.
aggregate is like:
if you have class like
#define interface struct
interface IA{public: virtual void f()=0;}
class A: public IA{...}
class B: public A{...}
change it to:
class B: public IA{IA& a; public: virtual void f(){a.f();}}
Can't agree more with kof99th(小虫). Yu Wo Xin You Qi Qi Yan. :))
Top
6 楼Hotman_x(小人)回复于 2002-06-04 22:42:48 得分 0
C++ 的优点在于:它为你提供无限可能,而到底实现哪种可能由你决定。Diamond 继承当然不可滥用,但不是不可用。标准库中的 iostream 不就用了吗?当你面对复杂的对象系统时,如果效率不是重要考虑的话,无论是组合还是接口,都不如多重继承更能准确的反映问题域的对象关系。Top
7 楼kof99th(小虫)回复于 2002-06-05 11:55:07 得分 0
呵呵,于我心有戚戚焉!
往深里想,就是C++的模型设计问题,他把问题留给我们,你觉得好就用,不好就不用,不就是了。任何事情都是有另外的办法可以解决的啊。Top
8 楼huxw()回复于 2002-06-05 20:18:08 得分 10
to ajoo
显然是开发人员的错啊。 ;)
多继承能够更加自然的描述我们的世界,不是吗?当我们构建一个自洽的库的时候,多继承可以给库的设计带来很多便利,就如Hotman_x提到的,iostream是一个很好的例子。
aggregate确实是不错的idea,但是如果程序员一定要直接访问A的成员变量呢?我知道这不是好的做法,提出来纯粹是为了抬杠 ;)Top
9 楼huxw()回复于 2002-06-05 20:19:05 得分 0
to kof99th
//hand
Top
10 楼ajoo(聪明的一猪)回复于 2002-06-05 22:47:53 得分 0
assembly 的优点在于:它为你提供无限可能,而到底实现哪种可能由你决定.
C 的优点在于:它为你提供无限可能,而到底实现哪种可能由你决定.
goto 的优点在于:它为你提供无限可能,而到底实现哪种可能由你决定.
"无论是组合还是接口,都不如多重继承更能准确的反映问题域的对象关系."
No. multi-inheritance normally does not scale up well. you may not have diamond now, but later, when the software evolves, you may suddenly suffer it. This is because subclassing has very tight coupling between base class and sub class.
Even conceptually, multi-inheritance is not always natural. in many cases, you should use MI only for interfaces, not for implementations.
the other non-natural thing about MI is, the order you write the base class matters.
i.e.
class A: public B, C{} is different than class A:public C, B{}
I know many libs use MI. Like ATL. But, the key is: you have to know how problematic MI can become and use it in a very disciplined way. Just as how people treat "goto".
Top
11 楼prototype(原型)回复于 2002-06-06 13:04:48 得分 0
> 1. diamond inheritance. B:public A; C:public A; D:public B, C;
> Although C++ has virtual inheritance to solve this.
> But it is only making things much more complex. Simply a mess.
in what sense, it is simply a mess?
> He who can stand out and tell how virtual inheritance works, how > the memory layout would be after virtual inheritance, please
> raise your hand!
a common implementation is using pointers to the virtual base in the non-virtual bases.
> Also, for virtual functions, the "this" pointer has to be moved
> up and down by the caller and callee.
this is a price you have to pay for this kind of polymorphism. c++ compiler implements that for you. if you don't want that, either don't use mi (instead, use some workaround via single inheritance or whatever methods, but think about the price you have paid for the extra code you did), or emulate it by yourself. maybe you can do it better for your special cases, but it will be hard to do it better for general cases.
> "但这不是多继承的错"
> then whose fault is it?
if you have to call it a fault, imo, it is the designer's fault, or if it is not the designer's fault, it the problem's fault. well, if it is the problem's fault, then it is your fault to take the problem, or it is your boss' fault to have you to take the problem... hehehe. in short, mi is a tech, or a solution for some problems, it is only bad when you misuse it. don't tell me it is totally useless, and its existence is simply and purely an evil.
> you have to know how problematic MI can become and use it in a
> very disciplined way. Just as how people treat "goto".
agreed. as always, it is dangerous for one to use something that s/he thinks s/he knows it but actually not.
Top
12 楼ajoo(聪明的一猪)回复于 2002-06-06 22:33:18 得分 0
prototype, you are welcome to give us a lecture on how virtual inheritance is implemented.
MI is not totally useless just as "goto" is not totally useless.
But, it is nearly useless and almost always detrimental to the system. :)
Of course, gurus can use "goto" and MI smartly. But the problem is that there's no such certificate to ensure you are a "guru". While many people think they are "guru" enough.
Also, C++ is claiming its performance advantage over Java. But when you use more and more fancy features like MI, smart pointer, gc. the program will be slower and slower and finally similar to Java.
If we say "well, you have to pay it", why not use Java? much cleaner.Top
13 楼ajoo(聪明的一猪)回复于 2002-06-07 04:02:19 得分 0
prototype, really want to learn from you about the virtual inheritance. I've done some experiment before but could not get a clear picture of it.
1.
class Base{
int i;
public:
Base(int a):i(a){}
};
class Der1: virtual public Base{
public:
Der1(int a):Base(a){}
};
class Der2: virtual public Base{
public:
Der2(int a):Base(a){}
};
class Der3: public Der1, public Der2{
public:
Der3(int a, int b):Der1(a), Der2(b){}
};
what'll be the value of Base::i after
Der3(1, 2);?
2.
what is the impact of virtual base class on virtual functions? what is the memory layout of the following example?
class Base{
public:
virtual void f()=0;
};
class Der1: virtual public Base{
public:
virtual void f(){cout<<1<<end;}
};
class Der2: virtual public Base{
public:
virtual void f(){cout<<2<<end;}
};
class Der3: public Der1, public Der2{
};
3.
what is the impact of virtual base class on objects without diamond structure? Such as:
class Der: virtual public Base{};
Der* p = new Der();
Top
14 楼ajoo(聪明的一猪)回复于 2002-06-07 04:26:37 得分 0
4.
also, what is the result of:
Base1: virtual Base
Der1: public Base1
Der2: public Base1
Der3: Der1, Der2
will Der3 have two copies of Base1 but only one copy of Base?
5.
for question 1,
what is the memory layout for
static_cast<Base*>(static_cast<Der2*>(new Der3()));?
what is
static_cast<Base*>(static_cast<Der1*>(new Der3()));?
If they are the same, how compiler achieve that?
Does compiler store a pointer to Base in any object of Der1 and Der2?
if this is true. it seems like it can solve the diamond problem (with performance sacrificed).
But how can this work with the situation where you have multple vptr's?Top
15 楼prototype(原型)回复于 2002-06-07 04:34:16 得分 0
> MI is not totally useless just as "goto" is not totally
> useless. But, it is nearly useless and almost always
> detrimental to the system. :)
i am not sure what 'nearly' and 'almost' exactly mean here.
> Of course, gurus can use "goto" and MI smartly. But the
> problem is that there's no such certificate to ensure
> you are a "guru". While many people think they are "guru" enough.
as you implied above, you have to know what you are doing, which
doesn't mean you have to be entitled 'guru'. i don't believe
mi is in a position that no one can understand how it works.
> Also, C++ is claiming its performance advantage over Java.
> But when you use more and more fancy features like MI,
> smart pointer, gc. the program will be slower and slower
> and finally similar to Java.
first, if you mean using mi, sp, gc will make one's c++
program as slow as java, i will doubt it.
> If we say "well, you have to pay it", why not use Java?
> much cleaner.
first, why use java if c++ is not worse? and if you have to
emulate mi in java, how can you avoid the overheads for
general cases?
Top
16 楼ajoo(聪明的一猪)回复于 2002-06-07 05:23:11 得分 0
"you have to know what you are doing"
many people think they know "what they are doing" when using "goto". Actually I don't think "goto" is hard to understand about how it works. :)
And, I believe inheritance can be even worse than "goto". With "goto", you are only messing around with your own function or module. But with MI, you are introducing coupling between modules. the changes to the base class by other people can effectively schew up your code (this is called versioning problem).
But, hey, let's stop this quite phylosophical debate. we've get across all we think, right?
I think MI is harmful to software design, bad performance, complex.
You think proper use of MI is good. performance tradeoff is worthy.
if we are not convinced by each other, don't think we can even if we continue for ever.
Could you teach me on the virtual base class issue? Thanks!Top
17 楼cloudwu(云风)回复于 2002-06-07 07:17:29 得分 10
我举个例子, 要用到菱形继承的.
假设我想自己实现 RTTI (仅仅是举例,有无实用价值先不考虑)
MFC 自己实现了 RTTI, 不过是比较丑陋的宏的形式, 如果我们完全不用
类似的宏, 怎么做? 我想用 template 来干, 这样就要用 MI 了.
我会做一个 template RTTI<class T,class B> (T 是 class, B 是T 的基类)
它里面提供了 RTTI 支持的函数, 比如类似 dynamic_cast , typeid, clone 等等.
如果做好了, 我希望 有 A->B->C 三个类
我写成
class A : virtual RTTI<A,NUL> {};
class B : public A, virtual RTTI<B,A> {};
class C : public B, virtual RTTI<C,B> {};
这样的形式.
可见, 不仅 A-B-C 是一条继承线, RTTI<A,NUL>-RTTI<B,A>-RTTI<C-B>
也应该是一条继承线, 这样, 才可以回溯到基类的一些信息.
里面需要一些技巧, 才好实现这样一个东西
比如定义 RTTI template 的时候, 需要类似这样的写法
template <class T,class B>
class RTTI : virtual RTTI<B,B::_base> {
protected:
typedef B _base;
};
这样才定义的出递归继承的 template
最后还需要对根上面的 template 特例化.
然后在根上面用模版方法的模式去调用虚函数, 来完成 RTTI 里面需要做的工作.
这样的实现, 是多个菱形的继承. 看起来很糟糕的说 :)
不过用起来比较方便, 我不知道还有没有更好的解决方法,
我曾经用 SI 也实现了类似的东西, 就是在 A-B-C 之间隔一个插入一个
RTTI template, 但是那样局限性就大了些.
请高手指点.Top
18 楼prototype(原型)回复于 2002-06-07 07:31:08 得分 10
> "you have to know what you are doing"
this is not restricted to knowing the syntax and semantics.
the primary reason for that people don't use 'goto'
everywhere is that they realize using it too much will do
harm to the readibility and maintainability. but if you
think a couple of 'goto' will enhance the r&m, you should
be encouraged to do so. setting a hard line for doing this
while not doing that is as harmful as using 'goto' wildly.
so is mi. as i said before, it is a tech having its own
application domain. if you think it will be harmful to your
overall design and there is better solutions. then why
bother messing around with a worse solution?
whether coupling is bad or not is up to your design -- what
you requires and what you want to achieve. single
inheritance also introduces coupling, people
can also screw your base classes with si, should it also be
forbidden because of this?
> I think MI is harmful to software design, bad performance,
> complex. You think proper use of MI is good. performance
> tradeoff is worthy.
yeah i think proper use of mi is good. the claim is correct,
but uselessly if 'proper' is not defined. and i can't define
it. if you think mi should be forbidden, then please be
specific about how mi can always be substituted by a
better solution for almost all design cases.
as for performance, i didn't mean it is worthy. whether
it is worthy or not depends on a particular case. what i
meant is that the complexity and overheads are, as i see,
undispensible for a general mi's implementation no matter
in what kind of languages. it is surely not c++'s fault.
i have replied about your questions about mi hours ago. but
when i came back, i found 登陆信息错误!!, even worse is that
i can get my what i wrote back. faint. (mozilla is still a
sh*t.) ok. write it again in short:
please don't regard it as 'learning from me'. i am not a
guru, and i can't guarantee what i write will be all correct
either, especially these days with the world cup... let's
learn it together. please correct me if i am wrong, and
raise anything you feel doubtful.
1. a compile error. the virtual base ctor have to be invoked
by the most derived class. so either you do this:
Der3(int a, int b):Der1(a), Der2(b), base(a){}
or define a default ctor for 'base'.
2.
implementation dependent. an implementation might be like this:
----------
ptr_2_base
der1
----------
ptr_2_base
der2
----------
der3
----------
vptr --------------------------> vtbl:
base der3::f(), delta(base)
----------
3.
just a simplified version of the above example:
-----------
ptr_2_base
der
-----------
vptr -----------------------> vtbl:
base der::f(), delta(base)
-----------
4.
yes.
5.
given the above examples, you tell me ba... :-)
Top
19 楼ajoo(聪明的一猪)回复于 2002-06-07 07:32:47 得分 0
if I have A:B, C
you'll need to write RTTI<A, B>, RTTI<A, C>, right?
why do you need class RTTI : virtual RTTI<B,B::_base> though?
wouldn't this:
template<class T, class B>
class RTTI
{
typedef B Base;
typedef RTTI<B, B::Base> MetaBase;
};
work?
Top
20 楼ajoo(聪明的一猪)回复于 2002-06-07 07:38:36 得分 0
class Base{
public:
virtual void f()=0;
void g(){f();}
};
class Der1: virtual public Base{
public:
virtual void f(){cout<<1<<end;}
};
class Der2: virtual public Base{
public:
virtual void f(){cout<<2<<end;}
};
class Der3: public Der1, public Der2{
};
what is the result of new Der3()->g();?Top
21 楼ajoo(聪明的一猪)回复于 2002-06-07 07:40:38 得分 0
"1. a compile error. the virtual base ctor have to be invoked
by the most derived class. so either you do this:
Der3(int a, int b):Der1(a), Der2(b), base(a){}
or define a default ctor for 'base'.
"
don't quite understand. how can a default ctor work? what's the semantics?Top
22 楼cloudwu(云风)回复于 2002-06-07 08:11:27 得分 10
我的例子可能举的不好, 或者说的不清楚
RTTI template 里面还需要有虚函数, 来完成一些扩展的功能.
比如模拟一个 dynamic_cast 来检查
downcast 是否有效. 比如检查一个 A* 是不是指向的一个 B 对象
独立的为A,B,C 每个 类给一个 RTTI template 是不够的.
我实际中类似问题并不是用来解决 RTTI 的, 比 RTTI 复杂的多.
这里用 RTTI 举例, 也没有仔细考虑是否恰当.Top
23 楼prototype(原型)回复于 2002-06-07 09:27:57 得分 10
what is the result of new Der3()->g();?
compile time error due to ambiguity.
how can a default ctor work? what's the semantics?
make a correction:
Der3(int a, int b):Der1(a), Der2(b), base(a){}
should be
Der3(int a, int b):base(a), Der1(a), Der2(b) {}
.
------------------------
Der3(int a, int b):base(), Der1(a), Der2(b) {}
^^^^^^
automatically added by the compiler if you have such a ctor for 'base'.
Top
24 楼ajoo(聪明的一猪)回复于 2002-06-07 22:13:17 得分 0
new Der3()->g(); compile error?
g() is not virtual ah.
what if I say
Base* pb = new Der3();
will that work?
then pb->g(); what is the result?
if you say
Der3(int a, int b):base(), Der1(a), Der2(b) {}
and the ctor of Der1 and Der2 will also call base(a) and base(b), what is the semantics?
Top
25 楼ajoo(聪明的一猪)回复于 2002-06-07 22:14:23 得分 0
cloudwu(云风):
but without a macro or language support, how can you generate typeid? with macro, you can say #type to get a string.Top
26 楼ajoo(聪明的一猪)回复于 2002-06-07 22:24:40 得分 0
prototype:
I don't think the ban of "goto" has been harmful so far. don't think the ban of "pointer" in Java has been harmful. if a thing is 90% bad, and 100% replacable, don't see why banning it would be harmful.
Let me reorganize my points:
1. inheritance itself has to be used with caution because of coupling problem.
2. MI, besides the problem of inheritance, also has performance problem and diamond inheritance problem. Although C++ gives a virtual base class, the solution is not elegant and complex.
But, you raised a good point: how do we replace mi or inheritance in general situation?
Well, I believe aggregation is good enough here, as many people already said.
Do you like to discuss the cons and pros of aggregation? I think that's more detailed and easier to draw a conclusion than simply arguing "ban MI vs. proper use of MI is necessary".Top
27 楼cloudwu(云风)回复于 2002-06-07 23:10:28 得分 0
ajoo(jet pig)
你完全可以在 template 里声明一个 static 变量.
每个类的 cpp 里都为这个变量初始化一个静态值,(dword 或 string)
如果你不给出, 编译器是不会让你过的, 所以是强制叫你写了 :)
这样比用MFC里类似的宏好的多.
ps. 我只是用 RTTI 举例.Top
28 楼ajoo(聪明的一猪)回复于 2002-06-08 01:46:35 得分 0
but then programmer is responsible for the uniqueness of the id?
too hard bah?Top
29 楼prototype(原型)回复于 2002-06-08 05:14:27 得分 10
> new Der3()->g(); compile error?
sorry, i didn't make it clear. class 'der3' has ambiguity.
in the case like your example, der3 must have a 'f()' to
override the 'f()'s from the bases, since at the
time of figuring out the vtbl for der3, the two 'f()'s from
'der1' and 'der2' have the same priority to be chosen. so
the compiler won't let you have such a definition for 'der3'.
> if you say
> Der3(int a, int b):base(), Der1(a), Der2(b) {}
> and the ctor of Der1 and Der2 will also call base(a) and base(b),
> what is the semantics?
oh. the base's ctor will be invoked by the most derived class.
the calls to the base's ctor in 'der1' and 'der2' will be ignored.
that is why (at least in part) the 'base' is called 'virtual'?
my view of banning "goto" is actually banning bad coding
styles that makes r&m very difficult, not simply "goto"
itself. fortunately, almost every suggestion against goto
has this reason stated (if not, the suggestion would be
meaningless). to me, the ban of 'goto' is still not rigorous, but
flexible due to that. in reality, i do have a few functions
that use 'goto', making my code short and clean.
another "bad" thing in c++ is 'macro', which is also labeled
'evil' due to various reasons. but it is still used widely
and smartly everyday. why?
i am not saying we should stick to what we have now, never
evolve our methods and tools. i am trying to say: we must
understand the true motivation for a piece of suggestion,
instead of saying or accepting it as "this is a law, violation
will be punished", since this is harmful, no doubt about it.
> Let me reorganize my points:
> 1. inheritance itself has to be used with caution because
> of coupling problem.
sure.
> 2. MI, besides the problem of inheritance, also has performance
> problem and diamond inheritance problem. Although C++ gives a
> virtual base class, the solution is not elegant and complex.
imho, the performance problem of mi is not that much. for
the scheme presented above, compared to si, the extra overheads are:
1. One addition operation for each use of a member in the
2nd or subsequant base(s).
2. One memory reference and one addition operation for
accessing the members in the virtual base.
3. One test and one increment for assignments between pointers to
bases and those to the derived.
(anything else i missed here?)
is that too much? i would say that is not very much (at leas)
for most cases.
> Well, I believe aggregation is good enough here, as many
> people already said.
one problem about aggregation is that it is harder to understand.
for many cases, inheritance sounds more like a natural choice.
but no doubt, aggregation is more flexible. do you think
aggregation can replace mi for all general cases? i am not sure.
Top
30 楼ajoo(聪明的一猪)回复于 2002-06-08 06:11:14 得分 0
"do you think aggregation can replace mi for all general cases?"
Yes, I do.
Actually Java's success already proves that.
1. One addition operation for each use of a member in the
2nd or subsequant base(s).
yes.
2. One memory reference and one addition operation for
accessing the members in the virtual base.
yes.
3. One test and one increment for assignments between pointers to
bases and those to the derived.
yes. this is for any MI, not just diamond.
But this is quite something.
it happenes not only on explicit assignment.
All subsumption will suffer this. and subsumption is the way you do OO programming. you do it everwhere.
a simple assignment becomes such a big thing is both inefficient and non-elegant. (changing the pointer's actual value on upcasting is a bad hack! Confusing and dangerous. it also makes gc algorithms harder from my understanding of gc.)
by the way, macro is still useful because sometimes we really don't have any good way around. While if we can write it in function, it should be forbidden to write macro though.
for "goto", although people always claim that they need "goto", I never see one case where you don't have good way around. Just as that discussion about exiting for-loop, a function can simply solve it.
for MI, since aggregation is a good way around, can't see any reason for using MI.
Top
31 楼cloudwu(云风)回复于 2002-06-08 12:38:16 得分 0
ajoo(jet pig):
程序员是有责任提供唯一的 id, 这并不是难事.
可以用和类名相同的 string. 不要告诉我你会敲错string?
用宏来实现也是一样, 除非是机器生成的代码.
只要不用编译器提供的 RTTI, 自己来做, 最后都有提供
如何提供唯一 id 的问题.
甚至可以用一个特殊的集合来管理 id, 运行时, 在静态初始化的时候
可以检查全局的 id 是否有重复, 避免这个问题手段多着呢.
ps.这已经偏离了是否用 MI 的问题, 我认为C++标准委员会把
MI 的支持纳入标准有它存在的必要. 比如我举的这个例子,
个人并不认为别的方法解决会更漂亮一些. 如果你只认识到 MI
对你的程序会带来复杂度和不可控因素, 而引申到
"can't see any reason for using MI" 那就有必要从新虚心当好
学生, 好好再学习一下 C++ 了.
建议重读一遍 D&E, MI 存在的理由很充分.Top
32 楼huxw()回复于 2002-06-08 20:52:06 得分 10
虚拟多继承结构并非这样难以理解。
当class A : public virtual Base{};时,为了表示A虚拟继承了Base,A中存在一个指针,(间接)指向Base这个子类型的位置。同理假设class B : public virtual Base{};,然后class C : public A, public B{};这个时候,A中间接指向Base的指针和B中简介指向Base的指针最终指向的目的是相同的,同一个Base。至于如何间接指向,和编译器相关,除非要二进制重用你的代码,否则应该没有需求知道。 ;)
虚拟继承带来的空间代价,每一个虚基类需要一个指针,为了实现虚拟继承可能需要一个额外的指针(也可能不需要)。时间代价,类型转化的时候需要一次多余的指针访问动作。
我不认为mi是那么糟糕的东西,具体的内容,d&e里面说得很多了,你不希望我抄书吧。java也无非是一种妥协,没必要看得起这种妥协,看不起另一种妥协吧。Top
33 楼ajoo(聪明的一猪)回复于 2002-06-10 23:15:10 得分 0
cloudwu(云风) :
show how do you use MI to implement your requirement.
It's good to have something we can discuss "whether we have to use MI".
Although I don't like your way of giving type id. you rely on the dynamic check instead of the type system. programmers are human being. what's wrong if I typo the class name? "Useramne" for "Username"? the design is so delicate bah?
Top
34 楼ajoo(聪明的一猪)回复于 2002-06-10 23:23:55 得分 0
by the way, if we allow programmers to input the type name individually, why don't we just let them implement the few rtti functions?
class A{
class TypeInfoImpl: public TypeInfo
{
public:
const char* getTypeName()const{return "A";}
const TypeInfo& getSuperType()const
{return B::getTypeInfo();}
};
static TypeInfoImpl info;
public:
static const TypeInfo& getTypeInfo()const{return info;}
};
Overall, the MI only saves you a few lines of keystrokes.Top
35 楼ajoo(聪明的一猪)回复于 2002-06-11 01:16:57 得分 0
macro cannot solve the name problem?
Well, it cannot work for "namespace". But, don't insult my dear macro. :)
#name can easily give the right name, no worry about typo.
"如果你只认识到 MI
对你的程序会带来复杂度和不可控因素, 而引申到
"can't see any reason for using MI" 那就有必要从新虚心当好
学生, 好好再学习一下 C++ 了.
建议重读一遍 D&E, MI 存在的理由很充分."
Pa pa ya! Thank you you did not say "then you are the enemy of the people" :)
what's "D&E"? a book?
why do you guys always give either some famous name or famous book in discussion? Don't you think by yourself?
Top
36 楼huxw()回复于 2002-06-11 09:44:01 得分 0
D&E是一本书,C++的发展与演化(The Design and Evolution of C++)。是C++的发明者Bjarne Struostrup自己写的一本书。其中专门有一章谈论mi为什么被引入C++。
确实mi问题让B.S.自己说明比我们要有力的多 ;)
Top
37 楼anrxhzh(百宝箱)回复于 2002-06-11 10:39:22 得分 10
毫无疑问,MI应该谨慎使用。不过我对另一个问题更感兴趣,MI能够被aggregation完全地替代吗?这里“替代”一词的含义是:
1.aggregation能够实现MI的功能。
2.这种实现相对于MI更容易理解,更简洁。
聪明人会意识到,这是一个容易证伪不易证实的命题。因为即使发现在1000种情况下都满足条件,只要发现一种不满足条件的特例,就可以推翻这个命题。既然ajoo(jet pig)判了MI的死刑,我们的审判当然要实行无罪原则。下面我举一个特例,请陪审团注意:
设计一个射击游戏,背景是西部牛仔的决斗。我们有一个类型Comboy,表示牛仔的各种拔枪动作,另一个类型Window,表示游戏中的移动的窗口,可以用MI来定义一个类型ComboyWindow,表示运动中的牛仔,这个类型可以用在所有要求Comboy或者Window的场合中。Top
38 楼cloudwu(云风)回复于 2002-06-11 13:20:38 得分 0
"the MI only saves you a few lines of keystrokes"
我并不认为如此, 继续 RTTI 的例子, 不错, 你可以在每个类里多加几个小函数
来实现, 但是日后, 对 RTTI 的要求提高的怎么办?
比如你想让每个类都支持从一个 string new 出它自己.
你是选择再给每个类增加一个小函数, 还是开始就用 MI 来设计,
而只在 RTTI 的 template 里加一个函数?
anrxhzh(百宝箱) :
我对游戏的例子很有兴趣;) (因为我是专职做这方面的 R&D 的)
对于 牛仔 和 移动 的问题, 在我现在的 engine 里倒不是用 MI 解决的.
我把 牛仔 和 移动 分别做成了两个类. (分别继承于 atom 和 control)
而 atom 和 control 也有共同的基类 object
而在这个 engine 里 control 可以 bind 到 atom 上, 一个 object
可以 bind 上很多 control. 这样在运行时的绑定, 要比用 MI 设计一个新类
更实用一些.
Top
39 楼anrxhzh(百宝箱)回复于 2002-06-11 14:24:20 得分 0
久仰云风工作室的大名:-)
多继承在C++中有两种用途:
1.通过粘合两个逻辑上无关的类型来构造一个新的类型。
2.连接类型的接口和实现。
大部分对MI的争议都是针对1.的,牛仔的例子属于2.,拔枪属于牛仔的接口,控制引擎属于实现细节。我认为对于2.还是使用委托更加灵活一些。一般来讲,2.在对设计有完全的控制权时使用,1.在粘合两个来自第三方的类层次时使用。Top
40 楼anrxhzh(百宝箱)回复于 2002-06-11 14:33:46 得分 0
补充一句,千夫所指的钻石问题在2.的情况下才可能出现。我觉得对MI更加严峻的质疑应该来自1.Top
41 楼anrxhzh(百宝箱)回复于 2002-06-11 14:50:37 得分 0
更加严峻的质疑:水陆坦克是否应该从战船和战车中多重继承?Top
42 楼cloudwu(云风)回复于 2002-06-11 14:53:32 得分 0
赞同:) 我仔细用 MI 时间不长, 主要也是用作"连接类型的接口和实现"
用委托的话, 总觉得不如 MI 叙事自然 :(
我现在做的 engine, 也尽量想把对象和显示分开. 就是把自绘部分从对象
中隔离开. 感觉使用 MI 会自然一些.Top
43 楼cloudwu(云风)回复于 2002-06-11 14:56:49 得分 0
其实还有个过度设计的问题, 比如通常我们设计的每个对象都有一个 clone()
函数, 返回自己的指针. 这个函数是否应该用 template + MI 来实现.
算不算过度设计?Top
44 楼anrxhzh(百宝箱)回复于 2002-06-11 16:36:57 得分 0
>>千夫所指的钻石问题在2.的情况下才可能出现。
这句话考虑得不太周详。假如水陆坦克从战船和战车继承,战船从战和船继承,战车从战和车继承的话,迷人的钻石就又出现了。
车 战 船
\ / \ /
战车 战船
\ /
水陆坦克
Top
45 楼liu_feng_fly(笑看风云 搏击苍穹 衔日月)回复于 2002-06-11 16:55:48 得分 0
呵呵,有得必有失,就好象java,为了跨平台,弄了一个虚拟机出来,结果跨平台有了,效率却没有了。多重继承也是这样,当你考虑使用他的时候,首先要考虑的就是你是否能负担使用他给你带来的那些正面的和反面的效果。
c++从来都不强制你使用什么,当然,他也没有理由强制人们不使用多重继承,如果不喜欢,不用就是了,呵呵。Top
46 楼huxw()回复于 2002-06-11 18:46:46 得分 0
to anrxhzh(百宝箱)
如你自己说的。mi当归并两个对等,逻辑无关的类型的时候,是很直白的。但是你给的水路坦克的例子,显然战车和战船不满足这样的要求。
对于这样一个问题,我觉得只有单根继承一种方法。不是单根的系统,就没法淘汰mi。你觉得呢?Top
47 楼ajoo(聪明的一猪)回复于 2002-06-11 23:15:37 得分 0
cloudwu(云风):
actually, don't you see that my TypeInfo class can be templatized so that all you need to do is to say:
static TypeInfoImpl<B, A> info;
then all the changes can be made to the TypeInfoImpl without the user changing anything?
Also, macro can be used so that all you have to write in your class is:
DECLARE_RTTI(B, A)
but, that's quite like MS's solution.
anrxhzh(百宝箱) :
车 战 船 战车 战船 水陆坦克
simplistically, they can be declared as independent interfaces.
Well, you may say that 战车 is a 车 and 战.
OK. two approaches,
1. CombatVehicle is subtype of Vehicle and Combatable. (note, here, MI is only used for interface, not implementation)
2. use adapters. CombatVehicle2Vehicle, CombatVehicle2Combatable.
only a few more lines of keystrokes.
as for the implementation classes, use aggregation too.
What's impossible here?
although you need more keystrokes, but it does reduce the system coupling.Top
48 楼ajoo(聪明的一猪)回复于 2002-06-11 23:21:14 得分 0
"比如通常我们设计的每个对象都有一个 clone()
函数, 返回自己的指针. 这个函数是否应该用 template + MI 来实现.
算不算过度设计?"
hey, how do you write a general clone? use copy-con? but that's not general enough.Top
49 楼cloudwu(云风)回复于 2002-06-12 00:03:37 得分 0
我曾经是疯狂的宏爱好者,
但是, template 的出现可以替代 99.9% 的宏运用
MFC 的 RTTI 宏其实是很拙劣的实现方法, 其背景是早期的 VC 对
template 支持的很差.
宏并不利于代码的阅读的调试, 甚至没有代码自动生成器的时候.
DECLARE_RTTI(B, A) 这种写法, 不小心把基类名写错是很危险的.
ps. 切身体会, 我早几年的一个东西里的设计就是用这样的宏实现的,
copy-paste 代码的时候, 曾经写错过.
但是合理的组织 template 可以在编译时报出这种错误,
毕竟 宏在编译时做的事情比 template 少的多.
同样宏没有特例化的机制, 所以你在维护 RTTI 的代码时需要对基类
单独维护一个版本. 而用 template , 可以把单独的几个不同的地方
特例化出来.
而且宏的修改和调试都是相当麻烦的. 在你为你的 RTTI 宏增加一个功能
插入一个函数的时候, 会异常痛苦. (需要宏展开, 然后调试)
虽然 MFC 很伟大, 不要被 MS 的东西束缚住思想了.
关于 clone() 函数, 一个简单的版本如下:
template<class T>
class implement_clone
{
public:
T* clone() const { return new T(*this); }
};
class A : public implement_clone<A>
{
....
};
复杂的, 可能还要用 traits 技术.
只是举个例子而已. 不必拘泥于例子本身.
Top
50 楼ajoo(聪明的一猪)回复于 2002-06-12 00:29:56 得分 0
either:
#define DECLARE(B, A) private: static TypeInfoImpl<B, A> info; pubic: static TypeInfo& getTypeInfo(){return info;}
or write it explicitly.
you choose by yourself.
But don't think here you can use any template to replace macro.
But anyway, this can replace your favorite MI.
Also, if you just use copy-ctor to implement a common clone, it's typical over design. Don't do that. useless at all.
even if you insist on that, don't use MI. use aggregation instead.Top
51 楼ajoo(聪明的一猪)回复于 2002-06-12 01:22:24 得分 0
here the entire solution: (not compiled yet, but guess you should know what it's doing)
//typeinfo interface
struct TypeInfo{
virtual const char* getTypeName() const=0;
virtual const TypeInfo& getSuperType()const=0;
};
//rtti interface which all rtti class should implement.
struct Rtti{
virtual const TypeInfo& getTypeInfo()const=0;
};
//the template class that has most of our logic in.
template<class D, class B>
class TypeInfoImpl: public TypeInfo
{
public:
virtual const char* getTypeName() const{return name;}
virtual const TypeInfo& getSuperType()const{return B::getTypeInfo();}
TypeInfoImpl(const char* n):name(n);
private:
const char* const name;
};
//the top rtti class
struct Top : public Rtti{
virtual const TypeInfo& getTypeInfo()const {return _type;}
private:
class Type: public TypeInfo{
virtual const char* getTypeName(){return "Top";}
virtual const TypeINfo& getSuperType()const{throw "Top type does not have super type";}
}
static Type _type;
};
//hate macro? type it explicitly then. not a big deal.
#define DECLARE_RTTI(D, B) private: static TypeInfoImpl<D, B> _typeinfo; \
public: virtual const TypeInfo& getTypeInfo()const{return _typeinfo;}
//c++ does not allow inline initlization of static variable. Damn!
#define INIT_RTTI(D, B) TypeInfoImpl<D. B> D::_typeinfo(#D)
//here go the demos
class Base{
DECLARE_RTTI(Base, Top)
};
class Der: public Base{
DECLARE_RTTI(Der, Base)
......
};
INIT_RTTI(Base, Top);
INIT_RTTI(Der, Base);
Top
52 楼cloudwu(云风)回复于 2002-06-12 01:57:30 得分 0
你仅仅提供了 getTypeInfo,
如果我要求把
getTypeName()
getSuperType()
都可以直接调用, 而不用令人困扰的
getTypeInfo().getTypeName() 这样的方式调用呢?
那是不是要在宏里再加上一些 inline 函数
如果有更多的要求, 是不是要再加呢, 最后你的宏会越来越庞大.
那仅仅只是把本该写到另一个类, 然后继承下来的东西.
用宏的方式放到单一的类去了.用手工的方法, 把两个类写成了一个.
不错, 是可以用 aggregation 代替 MI
那么用 C 也可以自己做 vtable, 我可以用
object->vtbl->func() 在 C 里通过 struct 来模拟 C++ 的多态,
为什么还要用 C++ 呢?
Top
53 楼ajoo(聪明的一猪)回复于 2002-06-12 01:59:55 得分 0
Oops, TypeInfoImpl should be
template<class D, class B>
class TypeInfoImpl: public TypeInfo
{
public:
virtual const char* getTypeName() const{return name;}
virtual const TypeInfo& getSuperType()const{return B::_getTypeInfo();}
TypeInfoImpl(const char* n):name(n);
private:
const char* const name;
};
_getTypeInfo should be a static method defined in each rtti class.
Top
54 楼ajoo(聪明的一猪)回复于 2002-06-12 02:02:54 得分 0
令人困扰的? why? because of several more keystrokes?
Do you mean you want to put all functionalities into one class?
用 C 也可以自己做 vtable
Tai Gong le bah?
as i said, MI and even inheritance has coupling problem, so we avoid using them. Can you give your reason why you have to have MI? what's the benefit of Mi over aggregation? except less keystrokes?Top
55 楼cloudwu(云风)回复于 2002-06-12 02:07:36 得分 0
都说了clone只是举例了. 在真实应用中, 可能还要干很多事情.
也可能是通过 Factory 来构造,
还可能需要改变引用计数, 向容器注册, 等等.
这些都是可变的因素, 如果日后想修改怎么办? 还用宏吗?
Top
56 楼ajoo(聪明的一猪)回复于 2002-06-12 02:10:34 得分 0
we have to discuss specific requirements to do design. Without the requirement, it's of no use to talk about design.
so rather keep it simple.Top
57 楼cloudwu(云风)回复于 2002-06-12 02:11:11 得分 0
我使用 MI 并不多, 所以研究也不深刻, 但是在很多情况下,
我都是希望能够用更直观的方式来表述问题.
就好象刚才 c->ctbl->func() 的极端例子, 难道我们用 C++
仅仅只是想少敲点键盘?Top
58 楼ajoo(聪明的一猪)回复于 2002-06-12 02:13:14 得分 0
as I said, you are Tai Gang. :)
c++ has benefits. you don't want me to list them, right?
While, MI has problems, and is replacable.
my point is:
1. MI/inheritance has problem
2. aggregation can replace them and is more flexible.Top
59 楼cloudwu(云风)回复于 2002-06-12 02:14:50 得分 0
如果有那个需求了, 关于那个会复杂一些的 clone 的实现,
用一下 MI, 真的很糟糕吗?
如果两类对象的 clone 实现方法不同.
我们又需要统一管理, 增加一个虚基类,
导致的“菱形" 真的很可怕吗?
Top
60 楼ajoo(聪明的一猪)回复于 2002-06-12 02:18:03 得分 0
直观? what is 直观?
obj.getTypeInfo().getTypeName() is not as 直观 as
obj.getTypeName()?
it's hard to say which one is better.
From my point of view, certainly the first one!
Let's use your assumption, later, we may want to add a function called "getSiblings()";
Well, if you do inheritance, what if somebody already defined this function in their class? Remember, you are not the only one using this Rtti lib. don't want them to yell at you "what the heck are you doing?!"?
Do you know of "versioning problem"?
Top
61 楼ajoo(聪明的一猪)回复于 2002-06-12 02:19:13 得分 0
用一下 MI, 真的很糟糕吗?
yes. normally you system cannot scale well with MI in existence.
also, use aggregation to replace MI 真的很糟糕吗?
Top
62 楼cloudwu(云风)回复于 2002-06-12 02:28:48 得分 0
或许 clone() 的里子还不够好,
换个例子了.
如果我有很多类的对象, 它们可以用各种方式运动.
其中我完成了一种曲线运动,
我希望有一个曲线运动的对象.
直观的方法是从这个对象和曲线运动两者继承下来.
我也可以把曲线运动换成直线运动.
当我的容器驱动所有的对象开始运动的时候, 只需要对这些东西
发一个 move 的指令.
如果用聚合, 你需要给对象增加一个函数, 转发 move
当我再增加一个别的行动方式, 比如旋转时, 采用聚合
就需要再增加一个转发旋转的函数.
但是直接用 MI 组合在一起的话, 就可以不需要这个工作,
而且表述问题更清楚.
Top
63 楼ajoo(聪明的一猪)回复于 2002-06-12 02:34:24 得分 0
in your case,
an interface like
struct Moveable{
virtual void move()=0;
}
should be used.
then for the impl, as I'm not sure about your requirement, can't give detailee solution, but, I can tell you, you won't have to use MI.
Top
64 楼cloudwu(云风)回复于 2002-06-12 02:34:34 得分 0
当名字混淆的时候, 可以用
obj.rtti::getTypeName()
在 RTTI template 里 typedef 一个 rtti 就 ok 了.
不发生混淆的时候, 直接用既可.
你这样通过 obj.getTypeInfo().getTypeName() 的方式调用函数
其实比编译器优化过的 MI 的实现效率上更低.
而另一个 clone 的问题呢?
你希望 obj.getCloneInterface().clone() 这样去调用吗?Top
65 楼cloudwu(云风)回复于 2002-06-12 02:36:51 得分 0
的确, 我会有一个
struct Moveable{
virtual void move()=0;
}
的界面. 如果需要的时候, 我会向这个界面添加新的东西.
虽然整个工程可能要重新编译, 但是代码的改动是很小的.
当然也可以留下接口, 用 visitor 模式来扩展.
Top
66 楼ajoo(聪明的一猪)回复于 2002-06-12 02:41:18 得分 0
that guy yelled at you used getSibling() for a month. It always works. Now, with your new version, he has to change his code to say
MyClass::getSibling()?
also, a typical versioning problem is like:
struct A{
virtual void f()=0;
};
class B:public A{
public:
virtual void f(){......}
void getSiblings(){......}
};
And all the sudden, you add a virtual function getSibling() to A, the compiler won't complain, and you've silently ruined class B!!!Top
67 楼ajoo(聪明的一猪)回复于 2002-06-12 02:43:27 得分 0
Again, as long as you implement the functionality. It's of no point to require that the function has to be within a given class.
the user should not care whether call obj.getTypeName() or obj.getTypeInfo().getTypeName() at all!
as for your clone, it's over design, without specific requirement, I would not implement it at all.
Top
68 楼cloudwu(云风)回复于 2002-06-12 02:50:50 得分 0
"that guy yelled at you used getSibling() for a month. It always works. Now, with your new version, he has to change his code to say
MyClass::getSibling()?"
如果它需要注明函数的域, 已经说明它定义出一个同名的函数了.
如果你害怕这种事情的发生, 可以强制使用
obj.rtti::getTypeName() 来调用.
但是意义明确的函数名, 是不应该发生混淆的.
如果发生这种事情, 无论多继承还是单继承都有可能发生.
对于第2个问题, 现在大多数编译器会 warning 的
Top
69 楼cloudwu(云风)回复于 2002-06-12 03:02:26 得分 0
clone 只是举个例子, 这里没办法临时写出很复杂的具体运用.
ok. 今天就到这里了 :) 很晚了这里,明天还要上班.
我在项目中用 MI 时间不久, 之前多年都是绕开 MI 的.
所以也不能立刻列举出太多的切身体会的例子.
不过是我读完了 D&E 关于 "MI 为什么存在"的章节后,改变了一些想法.
先了解再使用, 心里有数. 这样, 带来的问题都能避免的.
而编译器在它的层面做的优化一直都比人工的高的.
好比 C 去模拟 C++ 的类实现, 永远只能把 this 当第一个参数压栈传递.
而 C++ 编译器可以放在 ecx 里通过寄存器传递, 效率上就要高一分.
一些看起来很自然用 MI 的问题, 绕过 C++ 的特性去换别的方式
解决. 减低了隐含在 C++ 代码后面的东西的复杂度, 但可能牺牲了
编译器优化的余地.Top
70 楼anrxhzh(百宝箱)回复于 2002-06-12 10:22:22 得分 0
好夜战!
继承vs组合
这里的继承仅指对接口的继承,包括单继承和多继承,并且不考虑钻石问题,因为这只是多继承的实现细节。
继承相对于组合,直观,简单,静态。
组合相对于继承,不直观,复杂,动态。
由于组合的动态性和继承的被滥用,Design Patterns 在引言中导出了这样的原则:优先使用组合,而不是类继承。但是考虑到继承的直观和简单,我并不认为组合能完全地代替继承。另外,在组合两个类型时,我们可能会很难决定以哪个类型为主。特别的,在C++中的类型系统中,多继承出来的新类型可以向上转换为任何一个超类型,这是组合所不具备的优势。Top
71 楼anrxhzh(百宝箱)回复于 2002-06-12 12:27:40 得分 10
to : huxw()
车 战 船
\ / \ /
战车 战船
\ /
水陆坦克
在上面的例子中,最上层的车、战、船显然是三个理论上不相关的类型,所以第二层的设计者认为从这样的类型中多重继承是合理的。但是第三层的设计者就没那么幸运了,钻石是不可避免的。当然,如果是纯粹的接口继承,就可以不用考虑钻石问题。我要修正一下我的结论:钻石问题仅存在于实现继承中。
单根体系没有理论上的合理性,就不谈它了。
Top





