`
weiyinchao88
  • 浏览: 1186693 次
文章分类
社区版块
存档分类
最新评论

C++对象布局及多态实现的探索(六)

 
阅读更多

后记

  结合前面的讨论,我们可以看到,只要牵涉到了虚继承,在访问父类的成员变量时生成的代码相当的低效,需要通过很多间接的计算来定位成员变量的地址。在指针类型转换,动态转型,及虚函数调用时,也需要生成很多额外的代码来调整this指针。象前一篇中对C170对象的obj.foo()和obj.f170()两次调用,传递到两个函数中的this指针居然是不一样的。
  前面我们碰到过的怪异行为还有很多,比如偏移值指针指向地址的前4字节,及C150、C170对象中的4字节0值的语义,为什么对C150和C170调用foo函数时,this指针指向的不是子类部分的起始位置而是祖父类的起始位置,等等。去彻底的探究这些问题的意义并不是很大。虚继承的实现属于编译器厂商的行为,厂商出于不同的考虑,实现的方法也会大相径庭。
  对于传统的C程序员,他们可能会认为C++的效率低。其实效率低是低在多态部分,因为这要在运行时通过虚表来决议出函数的地址。但对于设计而言,多态是一个非常强大的武器。多态也是面向对象设计的核心技术之一。虽然在执行的效率上有所损失,但对于大规模的程序设计,对于问题域到模型的映射,使用以多态为核心的面向对象设计技术可以提高设计、实现及维护的效率,对于大部分的应用,总体来说得大于失。
  但是对于虚继承,个人感觉只是为了解决菱形继承及更复杂继承问题不得已而引入的一项机制,而且没有完美的解决方案,不但大幅的损失效率,而且带来了巨大的复杂性,使得继承结构晦涩难懂。如非万不得已,且在自己清楚一切后果的情况下,建议不要使用。尤其是不要在被虚继承的基类中声明非静态的成员变量。
  C++支持多种编程范型,面向过程式的、数据抽象及封装、面向对象、现在又多了一种基于模板技术的泛型编程。我们以一个优秀的开源C++编程环境ACE(之所以叫编程环境,因为它提供了从类库到框架的多层次的支持)为例,看看在设计时的衡量及各种编程范型的运用契机。ACE分几个层次,依次为:OS适配层、wrapper facade层、框架层、服务组件层。OS适配层、wrapper facade层主要运用了数据抽象及封装,没有用到多态及虚机制。因为这两层的关键是效率。而且在这两层中的类的成员方法尽量的内联。其实不使用多态及虚机制的话,C++的效率和C应该是差不多的,但对象封装导致了大量的琐碎方法(如对成员变量的访问封装,即set,get方法),而方法调用的成本也是相当高昂的(需要保存及恢复当前的执行环境上下文,参数的传递及返回值可能产生很多的临时变量及对象等)。所以这两层通过内联来减少函数调用的开销,提高执行效率。在框架层则使用了大量的设计模式,大量使用了多态机制及泛型技术。在这一层的主要关注点是结构的清晰,及实现设计上的语义。在这时多态机制在执行效率上的损失是可以忽略不计的。

  最后,我用Lippman在他的经典书籍《Inside the C++ Object Model》中关于描述虚成员函数章节中的一段话来做为这系列文章的结束。“Although I have a folder full of examples worked out and more than one algorithm for determining the proper offsets and adjustments, the material is simply too esoteric to warrant discussion in this text. My recommendation is not to declare nonstatic data members within a virtual base class. Doing that goes a long way in taming the complexity.”大意为在虚继承时用以确定偏移地址及进行this指针调整的可行算法很多,而且大都非常的诡异(这个我们已经见识了:))。同时他建议不要在被虚继承的基类中声明非静态的成员变量,这样做纯属自取烦恼。

  另,感谢张水松和张建业这两个土人,在写这些文章时和他们进行了一些非常有益的探讨。最后也是他们提醒我,不要再深入下去,以免走火入魔。

  (全文完)

代码中所定义类的简单说明

C000:空类。
C010:带普通成员函数及/或变量的顶层类。
C011:带普通成员函数及/或变量的顶层类。
C012:带静态成员函数及变量的顶层类。
C013:从C010继承的有成员变量及覆盖父类普通成员函数的类。
C014:从C011继承的空类。
C015:从C010,C011继承的空类。
C016:从C015继承带成员函数及/或变量的类。
C020:从C010虚继承的带有自己的成员变量的类。
C021:从C010虚继承的带有自己的成员变量的类。
C030:从C020,C021继承的类。
C040:带虚函数的顶层类。
C041:带虚函数及变量的顶层类。
C042:带虚函数及变量的顶层类。
C043:带两个虚函数的顶层类。
C050:从C041继承的空类。
C051:从C041,C042继承的空类。
C071:从C043继承并重写父类第一个虚函数的类。
C082:从C041,C042继承带自己定义虚函数并重写父类的虚函数的类。
C100:从C041虚继承的带成员变量的类。
C101:从C041虚继承的带成员变量的类。
C110:从C100,C101继承的带成员变量的类。
C140:从C041虚继承并重写父类的虚函数的类。
C141:从C041虚继承并重写父类的虚函数的类。
C150:从C140,C141继承的类。
C160:从C041虚继承带有自定义虚函数并重写父类的虚函数的类。
C161:从C041虚继承带有自定义虚函数并重写父类的虚函数的类。
C170:从C160,C161继承的类。

  源代码

  附件为打包的源码和VC7.1工程文件,解开后可自己调度运行。
  如果不下载附件请把所有的代码拷贝到一个CPP文件中即可。请注意用VC7.1进行编译(我只在VC7.1下验证过本代码)并一定把代码生成时的结构成员对齐选项设置为1字节,默认为8字节。否则运行时会有指针异常,请参见第一篇开始部分的说明。
  (附件请到这里下载,在页面的最底端。)

#include <iostream></iostream>
#include <iomanip></iomanip>
using namespace std;

struct C000
{
};

struct C010
{
C010() : c_(0x01) {}
void foo() { c_ = 0x02; }
char c_;
};

struct C011
{
C011() : c1_(0x02), c2_(0x03) {}
char c1_;
char c2_;
};

struct C012
{
static int sfoo() { return 1; }
int foo() { return 1; }
char c_;
static int i_;
};
int C012::i_ = 1;

struct C013 : public C010
{
C013() : c1_(0x01) {}
void foo() { c1_ = 0x02; }
char c1_;
};

struct C014 : private C011
{
};

struct C015 : public C010, private C011
{
};

struct C016 : C015
{
C016() : i_(1) {}
int i_;
};

struct C020 : public virtual C010
{
C020() : c_(0x02) {}
char c_;
};

struct C021 : public virtual C010
{
C021() : c_(0x03) {}
char c_;
};

struct C030 : public C020, public C021
{
C030() : c_(0x04) {}
char c_;
};

struct C040
{
virtual void foo() {}
};

struct C041
{
C041() : c_(0x01) {}
virtual void foo() { c_ = 0x02; }
char c_;
};

struct C042
{
C042() : c_(0x02) {}
virtual void foo2() {}
char c_;
};

struct C043
{
virtual void foo1() {}
virtual void foo2() {}
};

struct C050 : C040
{
};

struct C051 : public C041, public C042
{
};

struct C071 : C043
{
virtual void foo1() {}
};

struct C082 : public C041, public C042
{
C082() : c_(0x03) {}
virtual void foo() {}
virtual void foo2() {}
virtual void foo3() {}
char c_;
};

struct C100 : public virtual C041
{
C100() : c_(0x02) {}
char c_;
};

struct C101 : public virtual C041
{
C101() : c_(0x03) {}
char c_;
};

struct C110 : public C100, public C101
{
C110() : c_(0x04) {}
char c_;
};

struct C140 : public virtual C041
{
C140() : c_(0x02) {}
virtual void foo() { c_ = 0x11; }
char c_;
};

struct C141 : public virtual C041
{
C141() : c_(0x03) {}
virtual void foo() { c_ = 0x12; }
char c_;
};

struct C150 : public C140, public C141
{
C150() : c_(0x04) {}
virtual void foo() { c_ = 0x21; }
char c_;
};

struct C160 : public virtual C041
{
C160() : c_(0x02) {}
virtual void foo() { c_ = 0x12; }
virtual void f160() { c_ = 0x12; }
char c_;
};

struct C161 : public virtual C041
{
C161() : c_(0x03) {}
virtual void foo() { c_ = 0x13; }
virtual void f161() { c_ = 0x13; }
char c_;
};

struct C170 : public C160, public C161
{
C170() : c_(0x04) {}
virtual void foo() { c_ = 0x14; }
virtual void f170() { c_ = 0x14; }
char c_;
};

#define PRINT_SIZE(CLASS_NAME) /
cout << "The size of " << #CLASS_NAME << " is " << sizeof(CLASS_NAME) << endl;

#define PRINT_DETAIL(CLASS_NAME, OBJECT_NAME) /
cout << "The detail of " << #CLASS_NAME << " is "; /
for (int i = 0; i < sizeof(OBJECT_NAME); ++i) { /
cout.fill('0'); /
cout << setw(2) << hex << ((short)((char*)(&(OBJECT_NAME)))[i] & 0x00ff); /
cout << ' '; } /
cout << setw(0) << dec << endl;

#define PRINT_SIZE_DETAIL(CLASS_NAME) /
PRINT_SIZE(CLASS_NAME) /
CLASS_NAME _##CLASS_NAME; /
PRINT_DETAIL(CLASS_NAME, _##CLASS_NAME)

#define LF /
cout << endl;

template<typename t=""></typename>
void *
get_obj_addr(const T & obj)
{
return (void*)&obj;
}

void *
get_vp_addr(void * start, int offset)
{
return (void*)((char*)start + offset);
}

void *
get_vt_addr(void * vp)
{
return (void*)*((void**)vp);
}

void *
get_vti_val(void * vt, int idx)
{
return (void*)*((void**)((int*)vt + idx));
}

#define PRINT_VTABLE_ITEM(OBJ, VPOFFSET, INDEX) /
{ /
cout.fill(' '); /
cout << setw(8) << left; /
cout << #OBJ << " :"; /
void * obj_addr = get_obj_addr(OBJ); /
cout << hex << " objadr:" << obj_addr; /
void * vp_addr = get_vp_addr(obj_addr, VPOFFSET); /
cout << " vpadr:" << vp_addr; /
void * vt_addr = get_vt_addr(vp_addr); /
cout << " vtadr:" << vt_addr; /
void * vti_val = get_vti_val(vt_addr, INDEX); /
cout << " vtival(" << INDEX << "):" << vti_val << dec << right << endl; /
}

#define PRINT_OBJ_ADR(OBJ) /
cout << #OBJ << "'s address is : " << hex << get_obj_addr(OBJ) << dec << endl;

#define PRINT_PT(PT) /
cout << #PT << "'s value is : " << hex << PT << dec << endl;

struct __declspec(novtable) C180
//struct C180
{
C180() {
foo();
this->foo();
}
virtual foo() {
cout << "<< C180.foo this: " << this << " vtadr: " << *(void**)this << endl;
}
};

struct C190 : public C180
{
C190() {}
virtual foo() {
cout << "<< C190.foo this: " << this << " vtadr: " << *(void**)this << endl;
}
};

int
main(int argc, char * argv[])
{
PRINT_SIZE_DETAIL(C000)
LF
PRINT_SIZE_DETAIL(C010)
PRINT_SIZE_DETAIL(C011)
LF
PRINT_SIZE_DETAIL(C012)
LF
PRINT_SIZE_DETAIL(C013)
LF
PRINT_SIZE_DETAIL(C014)
PRINT_SIZE_DETAIL(C015)
LF
PRINT_SIZE_DETAIL(C016)
LF
PRINT_SIZE_DETAIL(C040)
LF
PRINT_SIZE_DETAIL(C050)
LF
PRINT_SIZE_DETAIL(C041)

{
LF
C040 obj1, obj2;
PRINT_VTABLE_ITEM(obj1, 0, 0)
PRINT_VTABLE_ITEM(obj2, 0, 0)
LF
C040 c040;
C050 c050;
PRINT_VTABLE_ITEM(c040, 0, 0)
PRINT_VTABLE_ITEM(c050, 0, 0)
}

{
LF
C043 c043;
C071 c071;
PRINT_SIZE_DETAIL(C071)
PRINT_VTABLE_ITEM(c043, 0, 0)
PRINT_VTABLE_ITEM(c071, 0, 0)
PRINT_VTABLE_ITEM(c043, 0, 1)
PRINT_VTABLE_ITEM(c071, 0, 1)
}

{
LF
PRINT_SIZE_DETAIL(C041)
PRINT_SIZE_DETAIL(C042)
PRINT_SIZE_DETAIL(C051)
C041 c041;
C042 c042;
C051 c051;
PRINT_VTABLE_ITEM(c041, 0, 0)
PRINT_VTABLE_ITEM(c042, 0, 0)
PRINT_VTABLE_ITEM(c051, 0, 0)
PRINT_VTABLE_ITEM(c051, 5, 0)
}

{
LF
PRINT_SIZE_DETAIL(C082)
C041 c041;
C042 c042;
C082 c082;
PRINT_VTABLE_ITEM(c041, 0, 0)
PRINT_VTABLE_ITEM(c042, 0, 0)
PRINT_VTABLE_ITEM(c082, 0, 0)
PRINT_VTABLE_ITEM(c082, 5, 0)
PRINT_VTABLE_ITEM(c082, 0, 1)
PRINT_VTABLE_ITEM(c082, 5, 1)
c082.foo3();

c082.C041::c_ = 0x05;
PRINT_VTABLE_ITEM(c041, 0, 0)
PRINT_DETAIL(C041, ((C041)c082))
PRINT_VTABLE_ITEM(((C041)c082), 0, 0)
LF
PRINT_VTABLE_ITEM(c082, 5, 0)
C042 * pt = dynamic_cast<c042></c042>(&c082);
PRINT_VTABLE_ITEM(*pt, 0, 0)
}

//PK change object's type by force
{
LF
C013 obj;
obj.foo();
((C010)obj).foo();
obj.C010::foo();
}

{
LF
C010 obj;
PRINT_OBJ_ADR(obj)
obj.foo();
C012::sfoo();
C010 * pt = &obj;
pt->foo();
}

{
LF
cout << "<<call function="" member="" virtual=""></call>>" << endl;
C041 obj;
PRINT_DETAIL(C041, obj)
PRINT_VTABLE_ITEM(obj, 0, 0)
obj.foo();
C041 * pt = &obj;
pt->foo();
}

{
LF
C051 obj;
C041 * pt1 = dynamic_cast<c041></c041>(&obj);
C042 * pt2 = dynamic_cast<c042></c042>(&obj);
pt1->foo();
pt2->foo2();
}

{
LF
C190 obj;
obj.foo();
PRINT_DETAIL(C190, obj);
PRINT_OBJ_ADR(obj);
}

{
LF
PRINT_SIZE_DETAIL(C020)
PRINT_SIZE_DETAIL(C030)
PRINT_SIZE_DETAIL(C100)
PRINT_SIZE_DETAIL(C110)
C020 c020;
c020.C010::c_ = 0x04;

PRINT_OBJ_ADR(c020);
C010 * pt = &c020;
PRINT_PT(pt);
pt->c_ = 0x03;
C010 * pt1 = 0;
C010 * pt2 = pt1;

C110 c110;
c110.c_ = 0x51;
c110.C100::c_ = 0x52;
c110.C101::c_ = 0x52;
c110.C041::c_ = 0x53;
c110.foo();
}

{
LF
PRINT_SIZE_DETAIL(C041)
PRINT_SIZE_DETAIL(C140)
PRINT_SIZE_DETAIL(C141)
PRINT_SIZE_DETAIL(C150)
C150 obj;
PRINT_OBJ_ADR(obj)
obj.foo();
C150 * pt = &obj;
pt->foo();
C141 * pt1 = dynamic_cast<c141></c141>(pt);
pt1->foo();
}

{
LF
PRINT_SIZE_DETAIL(C041)
PRINT_SIZE_DETAIL(C160)
PRINT_SIZE_DETAIL(C161)
PRINT_SIZE_DETAIL(C170)
C170 obj;
PRINT_OBJ_ADR(obj);
obj.foo();
obj.f170();
C170 * pt = &obj;
pt->f170();
pt->C041::c_ = 0x33;

C161 * pt2 = dynamic_cast<c161></c161>(pt);
pt2->c_ = 0x34;
pt2->foo();
pt2->f161();
}

return 0;
}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics