锐英源软件
第一信赖

精通

英语

开源

擅长

开发

培训

胸怀四海 

第一信赖

当前位置:锐英源 / 英语翻译 / COM原理翻译、最易学的COM组件开发引导系列-C++进阶首选
服务方向
人工智能数据处理
人工智能培训
kaldi数据准备
小语种语音识别
语音识别标注
语音识别系统
语音识别转文字
kaldi开发技术服务
软件开发
运动控制卡上位机
机械加工软件
软件开发培训
Java 安卓移动开发
VC++
C#软件
汇编和破解
驱动开发
联系方式
固话:0371-63888850
手机:138-0381-0136
Q Q:396806883

锐英源精品开源心得,转载请注明:“锐英源www.wisestudy.cn,孙老师作品,电话13803810136。”需要全文内容也请联系孙老师。

1.2.5 DB_VTBL: C++ Object Exposing "Pure" Abstract Base Class暴露“纯”抽象基类的C++对象。

1.2.5 .1 Problem: C++ Does Not Encapsulate Data Members问题:C++并没有封装数据成员

One problem with the bare C++ approach is that part of the implementation details of an object still have to be included in the declaration of the class: The client "sees" all the private member (arrays and so forth) even though the compiler will not allow a derived class to access them. 在使用无包装C++方法时,对象的部分实现细节依然包含在类的声明里:客户端“看到”所有的私有成员(数组等等),尽管编译器不允许派生类访问它们,这种能“看到”是个问题。

For the monolithic case, where everything is compiled from the complete source code, this approach is probably still acceptable, although not necessarily desirable.在整体融入情况下,编译器编译了全部代码,这个方法尽管不是最合意的,依然有可能被接受。

For binary (public) distribution of an object, revealing the implementation details in a header file is probably still too much. The client (specifically, the compiler, when compiling the client code) needs to know, at a minimum, the size of the data members when doing memory allocation for instances of the object, even if it never accesses any of the data members. When the object implementer changes the size of the data members, all clients must recompile. Our approach of having the object do the allocation lets you get away with not recompiling, because all the client code ever needs from the object is the addresses of its member functions. But I would definitely not feel comfortable about having a mission-critical system work with an internal discrepancy such as this.对于二进制(公开)发布的对象,在头文件里揭露实现细节依然太过了。客户端(具体指编译器当编译客户端代码时)至少需要知道数据成员的大小,来实现对象实例化,尽管它有可能永远也不会存取数据成员。当对象实现修改了数据成员的大小,所有客户端必须重新编译。因为所有客户端代码永远只需要知道它成员函数的地址,所以我们的目标是让对象进行分配,让你不再重新编译达到更易用。但是我不会明确地觉得满意,因为这类情况下对于关键事务系统工作会带有内部差异性。

As stated above, the only information the client needs from the object is the address of the member functions. Therefore, we could set up a table of pointers to all the (exported) member functions of the object. Then, when invoking the function, the client would simply look up the nth member function's address and jump to it.如上所述,客户端需要知道对象相关的函数。这样,我们能设置一个指针表指向所有(导出)成员函数。那么,当激活函数时,客户端只是简单地查找第n个成员函数的地址且跳转到它上面。

This approach sounds a little complicated to do in C++: We would have to set up a "wrapper class" to replicate the functions within the client code and, within each function, look up and call indirectly the corresponding member function in the object. Next, it would be convenient to have a tool that generates these trivial but tedious-to-implement wrapper functions.这个方法相比C++的方法略微复杂些:我们必须设置个“封装类”来在客户端代码里复制函数,在每个函数里,查找且非直接调用对象里对应函数。随后会讨论,有个工具来生成这些琐碎但是冗长实现的封装函数会方便些。

1.2.5.2 C++ Solution: Abstract Base Class C++的办法:抽象基类

Moving back from the binary world of member function addresses, let's get back to the higher levels of C++. There is a feature in C++ that provides a similar functionality: the abstract base class. 回到成员函数地址的二进制世界里,让我们回溯到C++高层级里。在C++里提供了类似的功能:抽象基类。

Common Syntax in Derived Classes派生类的常用语法

The term abstract base class sounds formidable, but actually it is nothing but a class that declares the syntax of one or more of its functions without implementing them. This class cannot be instantiated—it merely serves as a base class for other classes that fill in the missing "link." To be a "real" C++ class that can be instantiated, the deriving class must implement all the "abstract" functions with the exact same syntax.术语抽象基类听起来可怕,但是实际上没什么,它只是一个类,声明但没有实现它的函数而已。这个类不能实例化—它只是服务于派生功能,让别的类来“填空”。对于一个“真实”的C++类来说,它能实例化,派生的类也必须以同样的语法实现所有“抽象的”函数。

Calls to Operations in Derived Classes from Abstract Operations in the Base Class在基类里以抽象操作来调用派生类里的操作

There is another advantage to abstract base classes, besides letting you force derived classes to implement an exact function signature: They also let you use the unimplemented function from code in the base class and have C++ choose the actual code at run time.抽象基类的另外一个优点是,他们同样让你在基类代码里使用没实现的函数,让C++在运行时选择实际的运行函数。

You can provide operations in the base class that use other operations (the missing functions) to provide higher-level functionality—essentially, an abstract algorithm with replaceable elementary operations. You could implement a sorting algorithm as an abstract base class, one that uses comparisons and swaps as elementary operations. A derived class can then fill in these operations and the same algorithm can sort completely different objects.你能提供在基类里的操作,基类使用其它操作(遗失的函数)来提供高层级的功能—本质上来说,抽象规则是可替换的元素操作。你能够实现一个排序规则来做为一个基类,另外的程序员可以使用比较和交换操作来做为元素级操作。派生类能够填充这些操作和同样的规则,且同样的算法能够对不同对象完整地排序。

If two classes derive from the same abstract base class, they can share the same abstract operations defined in the base class, and yet they can provide very different functionality by providing different elementary operations. The code in the base class calls functions depending on the class of the actual instance of the object it operates on.如果2个类派生自同样一个抽象基类,他们能够共享基类里同样的抽象操作,且他们能够通过提供不同的元素操作来提供非常不同的功能。基类里的操作调用的函数依赖于对象的实例类型。

Common Interface to All Derived Classes面向所有派生类的常用接口

A side effect of this architecture is that you can cast a pointer to an instance of a class derived from an abstract base class back to the base class, and then you can use this pointer to invoke functions in the derived class.这个架构的副作用是你能转换派生类指针为基类类型指针,且能用这个指针来激活派生类里的函数。

If multiple classes derive from one abstract base class, a client can invoke the same function on two different objects and C++ will find—at run time—the correct function in the correct derived class.如果从抽象基类里派生了多个类,客户端能激活不同对象上的同名函数,C++在运行时会从派生类里查找对应正确的函数。

Implementation Secret: Virtual Functions实现的秘诀:虚函数

How does the compiler accomplish this magic? All the functions in an abstract base class must be virtual functions: You can call a virtual function on a pointer to a base class, and the compiler will call the correct implementation depending on the actual object referred to by the pointer.编译器怎样实现这个魔法呢?所有在抽象基类里的函数必须是虚函数:你能通过基类指针来调用虚函数,编译器会依赖指针指向实际对象的类型来调用正确的实现函数。

How does the compiler implement virtual functions? The compiler builds a table for each class that contains pointers to all the virtual functions declared in the class, whether they are declared (and possibly implemented) in a base class or declared in the derived class. The table is filled in "order of declaration"; that is, if there is no base class with virtual functions, the first declared member function in the derived class (source code order) corresponds to the first entry in the virtual function table for this class. If there are virtual functions declared in base classes, they go before the newly defined virtual functions. The virtual function table is also called the vtable of the class.编译器是怎样实现虚函数的呢?编译器会为每个类构造出表,这个表里包含所有虚函数的指针,不管虚函数是在基类或派生类里声明(可能被实现了)。表以“声明顺序”来填充;也就是说,如果基类里不带虚函数,在派生类里第一个声明的虚函数(源代码顺序)对应虚函数表的第一个入口。如果在基类里有虚函数,他们在派生类里新定义虚函数入口前面。虚函数表同样称为类的vtable。

Note the following: 注意如下情况:

The table is built on a per-class basis, not for each instance of the class.表是基于类构造,不是基于实例构造。

The memory image of an instance of a class has one pointer to the vtable of this class, stored in front of the actual data members of the object. Thus an object that uses virtual functions occupies 4 additional bytes for each instance. The vtable itself occupies 4 bytes for each virtual function, but there is only one vtable per class and it is shared by all instances. When you invoke a virtual function from a base class, the C++ compiler has a pointer to the object's instance data (the "this" pointer). It obtains the pointer to the vtable of the class from the first 4 bytes of the object's instance data. Each virtual function has a unique index in the vtable of a given class. The compiler simply obtains the function address from the vtable at the function's index and branches to this address. When you invoke calls through a pointer to the base class, the technique is the same.类的实例的内存快照里有一个vtable的指针,该指针位置在实际的成员变量前面。这样,使用虚函数的对象每个实例占用4个额外的字节。vtable里一个虚函数入口占用4个字节,但是对每个类只有一个vtable,它被所有实例共享。当你用基类激活一个虚函数时,C++编译器有一个指向对象实例数据的指针(this指针)。它包含类的vtable表的指针,包含位置是在对象实例数据的前4字节里。编译器简单地按照函数的索引和类的分支情况获取vtable里函数地址。当你通过基类的指针来调用虚函数,技术也是一样的。

Non-virtual functions do not appear in the vtable. vtable里不会有非虚函数。

Figure 1. Memory layout of an instance of an object with virtual classes. [Reprinted from "Object Mapping in C++," by Tom Germond, Jan Gray, Dale E. Rogerson. MSDN Library Archive, Technical Articles, C/C++ Articles.]

1.2.5.3 Using "Pure" Abstract Base Classes as Function Table Wrappers以“纯”抽象基类来实现函数表包装

When I described the problem of changing implementations of objects and I stated that the client needs only the addresses of the object's member functions, I proposed a tool to generate wrappers for tables with the exported functions of an object.当我描述修改对象实现问题时,我指出客户端只需要知道对象成员函数的地址,我展示了工具来生成对表的封装,表里有导出函数。

Well, here it is: your C++ compiler. As we saw above, virtual functions are implemented exactly that way: The compiler sets up a table with the addresses of the function (vtable) and lets you wrap these functions with an abstract base class that simply maps calls to members through the table. As you will see, we will use abstract base classes in our samples that provide no actual code—all the members will be pure virtual functions, and all they will do is let you invoke functions in any derived class, once you have cast a pointer to the base class. For the purposes of this article, I call this kind of abstract base class a pure abstract base class; COM calls it an interface.虚函数恰恰实现了我们的要求。我们会使用抽象基类,该类不提供实际的代码—所有成员是纯虚函数,他所能做的只是让你能够激活派生类里的虚函数。结合本文的目的,我称呼这些抽象基类为纯抽象基类;COM称呼它为接口。

Let's use this idea, to begin with, in a subdirectory of the DB sample that has everything compiled into one executable file. Even in this situation, using abstract base classes can be advantageous. You can make sure that you will not accidentally use implementation data that for technical reasons you had to make "public" for C++.现在DB例子里,所有代码编译到一个可执行文件里。在这样的情况下,使用抽象基类也有巨大的优越性。你能确保不会偶然使用实现的数据,这些数据在技术原因的压力下要求你让它成为“公有”的。

1.2.5.4 Changes: Step by Step逐步修改

Use the DB_CPPDLL sample as the basis. The next sample will be very similar to this one, so if you are short of time, you can skip this one. Its primary purpose is to illustrate that using abstract base classes as function table wrappers is a standard C++ feature, which can also be very useful in a non-component world. 使用DB_CPPDLL类做为基础。下一步的例子会和这个非常象,所以用时短,能忽略此步。它的主要目的是演示使用虚基类做为函数表封装者,这是个标准的C++特点,这在非组件环境下是经常用到。

Copy the header file (interface\dbsrv.h) to \object\dbsrvimp.h. The first file will contain the abstract base class, which is the only code the client needs. The second file will be the header for the actual implementation.拷贝头文件(interface\dbsrv.h)到\object\dbsrvimp.h。第一个文件会包含抽象基类,这是客户端唯一需要的。第二个文件是实际实现文件的头文件。

Make all functions in interface\dbsrv.h pure virtual functions and remove the data members. Change the name of the class from CDB to IDB (Interface to DB).把\dbsrv.h里的函数都修改为纯虚函数,删除数据成员。修改类名,从CDB修改到IDB(DB的接口)。

Derive CDB in object\dbsrvimp.h from IDB and include ...\interface\bdsrv.h. (Don't forget to change the #ifndef at the beginning of the file to something like _DBSRVIMP_INCLUDE.)从IDB派生类,新类命名为CDB,类对应文件为\dbsrvimp.h,里面要包含…\interface\dbsrv.h(不要忘记修改在文件开头位置类似_DBSRVIMP_INCLUDE 对应的#ifdef语句)。

Because the client can not instantiate an abstract base class, we will have to have the object instantiate itself and return a pointer to itself, cast to IDB*: Declare and define a CreateDB() function in dbsrv.h/cpp that does this. 因为客户端不能实例化抽象基类,我们必须让对象实例化自己,且返回个自己的指针,再转换到IDB*:声明且定义个CreateDB()函数,来完成上述工作,这个函数在drsrv.h/cpp里。

The resulting header file Interface\dbsrv.h: Interface\dbsrv.h文件里结果形式如下:

class IDB {

// Interfaces

public:

      // Interface for data access

     virtual HRESULT Read(short nTable, short nRow, LPTSTR lpszData) =0;

     virtual HRESULT Write(short nTable, short nRow, LPCTSTR lpszData) =0;

     (...)

};

HRESULT CreateDB(IDB** ppObj);

The new header "Object\dbsrvimp.h":

#include "..\Interface\dbsrv.h"

typedef long HRESULT;

class CDB : public IDB {

// Interfaces

public:

      // Interface for data access

     HRESULT Read(short nTable, short nRow, LPTSTR lpszData);

     (...)

};

Make dbsrv.cpp include dbsrvimp.h instead of dbsrv.h. 让dbsrv.cpp包含dbsrvimp.h来代替dbsrv.h。

The resulting DBSRV.CPP (no changes, just adding the instantiation function):DBSRV.CPP里有代码为(没修改,只是添加了实例化函数):

(...)

HRESULT CreateDB(IDB** ppObj) {

  *ppObj=(IDB*) new CDB; // Cast to abstract base class.

  return NO_ERROR;

}

Now we will adjust the client: Change CDB::m_pDB to IDB* and use CreateDB instead of new to instantiate it. 现在,我们要调整客户端:修改CDB::m_pDB到IDB*,且使用CreateDB来代替new来进行实例化。

Everything works just as before. If we look at the binary level, there is a slight overhead cost for the indirect function call (get vtable pointer from instance data, get function pointer from vtable, jump), but the performance measured with "Read Multiple" is not significantly affected. The actual "work" performed in the member functions takes orders of magnitude longer than these two additional memory accesses.如果我们从二进制这一层检查一下,这种非直接的函数调用有轻微的间接成本(从实例数据里获取vtable指针,从vtable里获取函数,跳转),但是在“读多个”情况下,性能不会有致命影响。在成员函数里执行的实际“工作”和2个步骤的额外内在存取工作,效率不在一个数量级上。

友情链接
版权所有 Copyright(c)2004-2021 锐英源软件
公司注册号:410105000449586 豫ICP备08007559号 最佳分辨率 1024*768
地址:郑州大学北校区院(文化路97号院)内