软件设计奇怪的重复模板模式(CRTP)
添加时间:2019-09-05 15:42:44
来源:
建议将虚函数和运行时多态性作为先决条件。下面是一个演示运行时多态性的示例程序。
filter_none
编辑
play_arrow
brightness_4
// A simple C++ program to demonstrate run-time
// polymorphism
#include <iostream>
#include <chrono>
using namespace std;
typedef std::chrono::high_resolution_clock Clock;
// To store dimensions of an image
class Dimension
{
public:
Dimension(int _X, int _Y) {mX = _X; mY = _Y; }
private:
int mX, mY;
};
// Base class for all image types
class Image
{
public:
virtual void Draw() = 0;
virtual Dimension GetDimensionInPixels() = 0;
protected:
int dimensionX;
int dimensionY;
};
// For Tiff Images
class TiffImage : public Image
{
public:
void Draw() { }
Dimension GetDimensionInPixels() {
return Dimension(dimensionX, dimensionY);
}
};
// There can be more derived classes like PngImage,
// BitmapImage, etc
// Driver code that calls virtual function
int main()
{
// An image type
Image* pImage = new TiffImage;
// Store time before virtual function calls
auto then = Clock::now();
// Call Draw 1000 times to make sure performance
// is visible
for (int i = 0; i < 1000; ++i)
pImage->Draw();
// Store time after virtual function calls
auto now = Clock::now();
cout << "Time taken: "
<< std::chrono::duration_cast
<std::chrono::nanoseconds>(now - then).count()
<< " nanoseconds" << endl;
return 0;
}
输出:
所用时间:2613纳秒
见这对上述结果。
当一个方法被声明为虚拟时,编译器会秘密为我们做两件事:
在类对象的前4个字节中定义VPtr
在构造函数中插入代码以初始化VPtr以指向VTable
什么是VTable和VPtr?
当一个方法在类中声明为virtual时,编译器会创建一个虚拟表(也称为VTable)并在该表中存储虚方法的地址。然后创建虚拟指针(也称为VPtr)并初始化以指向该VTable。VTable在类的所有实例之间共享,即编译器仅创建一个VTable实例,以便在类的所有对象之间共享。该类的每个实例都有自己的VPtr版本。如果我们打印包含至少一个虚方法的类对象的大小,则输出将是sizeof(类数据)+ sizeof(VPtr)。
由于虚拟方法的地址存储在VTable中,因此可以操纵VPtr以调用那些虚拟方法,从而违反封装原则。见下面的例子:
filter_none
编辑
play_arrow
brightness_4
// A C++ program to demonstrate that we can directly
// manipulate VPtr. Note that this program is based
// on the assumption that compiler store vPtr in a
// specific way to achieve run-time polymorphism.
#include <iostream>
using namespace std;
#pragma pack(1)
// A base class with virtual function foo()
class CBase
{
public:
virtual void foo() noexcept {
cout << "CBase::Foo() called" << endl;
}
protected:
int mData;
};
// A derived class with its own implementation
// of foo()
class CDerived : public CBase
{
public:
void foo() noexcept {
cout << "CDerived::Foo() called" << endl;
}
private:
char cChar;
};
// Driver code
int main()
{
// A base type pointer pointing to derived
CBase *pBase = new CDerived;
// Accessing vPtr
int* pVPtr = *(int**)pBase;
// Calling virtual method
((void(*)())pVPtr[0])();
// Changing vPtr
delete pBase;
pBase = new CBase;
pVPtr = *(int**)pBase;
// Calls method for new base object
((void(*)())pVPtr[0])();
return 0;
}
输出:
CDerived :: Foo()调用
调用CBase :: Foo()
我们能够访问vPtr并能够通过它调用虚拟方法。这里解释了对象的内存表示。
使用虚方法是明智的吗?
可以看出,通过基类指针,正在调度派生类方法。一切似乎都很好。那有什么问题呢?
如果多次调用虚拟例程(数十万的顺序),则会降低系统性能,原因是每次调用例程时,需要通过使用VPtr查看VTable来解析其地址。对虚拟方法的每次调用的额外间接(指针取消引用)使得访问VTable成为一项代价高昂的操作,最好尽可能地避免它。
2021-07
到目前为止,我们已经为我们OracleNetsuite的标题创建了导航栏。完成标题的下一件事是在图像上方包含图像和文本,如下面的屏幕截图所示:让我们再次查看index.html 文件中标题的部分代码。代码中突出显示的部分显示了标题的图像菜单:要完成图像菜单,我们首先需要在 id … [了解更多]
2021-07
响应式网站:响应式网站是旨在适合所有类型的设备并调整网站布局以最适合屏幕尺寸的网站。无需制作任何其他设备版本的网站以适应小型设备。移动网站:移动网站是专为适应手机、平板电脑等特别小的设备屏幕而设计的网站。需要制作网站的桌面版本以适应移动设备特定的桌面屏幕。响应式网站和移动网站的区… [了解更多]
2021-06
OracleNetsuitePython 提供了许多分发 Python 项目的方法。其中一种方法是使用一种称为 Docker 的重要技术。Docker 是一个开源应用程序,允许管理员使用容器创建、管理、部署和复制应用程序。它基本上是一个平台,使开发人员能够通过将应用程序放入容器中… [了解更多]
2021-05
财务负责人戴了两顶帽子:一是遵守法规,以确保公司的行为和会计正确无误,并遵守公司开展业务的不同司法管辖区的法规;二是遵守法规。一种战略,确保公司达到财务里程碑和成功指标。当一家公司上市时,包括通过特殊目的收购公司(SPAC)上市时,这两个角色尤其重要。Oracle NetSuit… [了解更多]
2021-03
公司财务人员中记账人员的工作内容:1、从钉钉中下载审批完成的8种审批类型的单据数据,包含合同付款、费用报销等2、记账人员根据付款的性质及费用归属,把记账分成6种形式:合同付款(工程、成本)、合同付款(其他)、非合同付款(工程、成本)、非合同付款(其他)、费用报销(工程、成本)、费… [了解更多]