Answers:
来自“ C ++中的虚函数”:
只要程序声明了虚函数,就会为该类构造av-table。v表由包含一个或多个虚拟功能的类的虚拟功能的地址组成。包含虚拟函数的类的对象包含一个虚拟指针,该指针指向内存中虚拟表的基地址。每当有虚拟函数调用时,v表都会用于解析到函数地址。包含一个或多个虚拟函数的类的对象在内存中对象的最开始处包含一个称为vptr的虚拟指针。因此,在这种情况下,对象的大小增加了指针的大小。此vptr包含内存中虚拟表的基地址。请注意,虚拟表是特定于类的,即 一个类只有一个虚拟表,而与它包含的虚拟函数的数量无关。该虚拟表又包含该类的一个或多个虚拟函数的基地址。在对象上调用虚拟函数时,该对象的vptr为内存中的该类提供虚拟表的基地址。该表用于解析函数调用,因为它包含该类的所有虚函数的地址。这是在虚拟函数调用期间解决动态绑定的方式。该对象的vptr提供该类在内存中的虚拟表的基地址。该表用于解析函数调用,因为它包含该类的所有虚函数的地址。这是在虚拟函数调用期间解决动态绑定的方式。该对象的vptr提供该类在内存中的虚拟表的基地址。该表用于解析函数调用,因为它包含该类的所有虚函数的地址。这是在虚拟函数调用期间解决动态绑定的方式。
我普遍认为答案是“否”。您可以进行一些内存修改以找到vtable,但是您仍然不知道函数签名的外观。无需直接访问vtable或在运行时对其进行修改,就可以使用此功能(该语言支持)实现任何目标。还要注意,C ++语言规范未指定需要vtable,但是大多数编译器都是通过这种方式实现虚函数的。
我认为这里的答案是“取决于实现”,因为该规范首先不需要vtables。但是,实际上,我相信所有现代编译器仅在一个类至少具有1个虚函数的情况下才创建vtable。与vtable相关联的空间开销与与调用虚拟函数与非虚拟函数相关联的时间开销。
答案是语言规范未指定,因此取决于实现。如果未定义(通常不是),则调用纯虚函数会导致未定义的行为(ISO / IEC 14882:2003 10.4-2)。实际上,它确实在vtable中为该功能分配了一个插槽,但并未为其分配地址。这使vtable不完整,这要求派生类实现该功能并完成vtable。有些实现只是在vtable条目中放置了一个NULL指针。其他实现则将指向虚拟方法的指针放置为执行与声明类似的操作。
请注意,抽象类可以为纯虚函数定义实现,但是该函数只能使用限定ID语法来调用(即,在方法名称中完全指定该类,类似于从a调用基类方法派生类)。这样做是为了提供易于使用的默认实现,同时仍然需要派生类提供重写。
这已成为我所学的知识,所以如果我错了,请有人在这里帮助我!
我相信只有类中的虚拟函数会遇到与调用虚拟函数和非虚拟函数有关的时间性能损失。该类的空间开销是任意一种。请注意,如果有一个vtable,则每个类只有一个,而每个对象只有一个。
我不相信被覆盖的虚函数的执行时间与调用基本虚函数相比会减少。但是,与为派生类和基类定义另一个vtable关联的类有额外的空间开销。
http://www.codersource.net/published/view/325/virtual_functions_in.aspx(通过回程机器)
http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/ cxx-abi / abi.html#vtable
不是可移植的,但是,如果您不介意卑鄙的把戏,请确保!
警告:不建议儿童,969岁以下的成年人或半人马座的小毛茸茸动物使用此技术。副作用可能包括从您的鼻子飞出的恶魔,突然出现的Yog-Sothoth作为所有后续代码审阅的必需批准者,或追溯添加
IHuman::PlayPiano()
到所有现有实例中]
在我见过的大多数编译器中,vtbl *是对象的前4个字节,而vtbl内容只是那里的成员指针数组(通常按它们声明的顺序,基类的第一个)。当然,还有其他可能的布局,但这就是我通常观察到的。
class A {
public:
virtual int f1() = 0;
};
class B : public A {
public:
virtual int f1() { return 1; }
virtual int f2() { return 2; }
};
class C : public A {
public:
virtual int f1() { return -1; }
virtual int f2() { return -2; }
};
A *x = new B;
A *y = new C;
A *z = new C;
现在要拉一些恶作剧...
在运行时更改类:
std::swap(*(void **)x, *(void **)y);
// Now x is a C, and y is a B! Hope they used the same layout of members!
为所有实例替换方法(monkeypatching类)
这有点棘手,因为vtbl本身可能位于只读内存中。
int f3(A*) { return 0; }
mprotect(*(void **)x,8,PROT_READ|PROT_WRITE|PROT_EXEC);
// Or VirtualProtect on win32; this part's very OS-specific
(*(int (***)(A *)x)[0] = f3;
// Now C::f1() returns 0 (remember we made x into a C above)
// so x->f1() and z->f1() both return 0
由于mprotect的操作,后者很可能会使病毒检查程序和链接唤醒并引起注意。在使用NX位的过程中,它很可能会失败。
还是仅调用虚拟函数?速度是否会受到影响(无论是否实际覆盖了虚函数),或者只要它是虚函数,速度就不会起作用。
拥有虚拟函数会减慢整个类的速度,因为在处理此类对象时,必须初始化,复制更多数据……。对于只有六个成员左右的班级,差异应该忽略不计。对于仅包含一个char
成员或根本不包含任何成员的类,差异可能会很明显。
除此之外,重要的是要注意,并非对虚拟函数的每个调用都是虚拟函数调用。如果您有一个已知类型的对象,则编译器可以发出用于正常函数调用的代码,甚至可以内联该函数。仅当您通过可能指向基类的对象或某个派生类的对象的指针或引用进行多态调用时,才需要vtable间接方式并为此付出代价。
struct Foo { virtual ~Foo(); virtual int a() { return 1; } };
struct Bar: public Foo { int a() { return 2; } };
void f(Foo& arg) {
Foo x; x.a(); // non-virtual: always calls Foo::a()
Bar y; y.a(); // non-virtual: always calls Bar::a()
arg.a(); // virtual: must dispatch via vtable
Foo z = arg; // copy constructor Foo::Foo(const Foo&) will convert to Foo
z.a(); // non-virtual Foo::a, since z is a Foo, even if arg was not
}
无论功能是否被覆盖,硬件必须采取的步骤基本相同。从对象中读取vtable的地址,从相应的插槽中检索函数指针,以及由指针调用的函数。在实际性能方面,分支预测可能会产生一些影响。因此,例如,如果您的大多数对象都引用了给定虚函数的相同实现,则分支预测器有可能甚至在检索指针之前就正确地预测要调用哪个函数。但是哪个函数是通用函数并不重要:它可以是大多数对象委托给非覆盖的基本案例,也可以是大多数对象属于同一子类,因此委托给相同的覆盖案例。
我喜欢jheriko的想法通过模拟实现来演示这一点。但是我将使用C来实现类似于以上代码的内容,以便更容易看到底层代码。
typedef struct Foo_t Foo; // forward declaration
struct slotsFoo { // list all virtual functions of Foo
const void *parentVtable; // (single) inheritance
void (*destructor)(Foo*); // virtual destructor Foo::~Foo
int (*a)(Foo*); // virtual function Foo::a
};
struct Foo_t { // class Foo
const struct slotsFoo* vtable; // each instance points to vtable
};
void destructFoo(Foo* self) { } // Foo::~Foo
int aFoo(Foo* self) { return 1; } // Foo::a()
const struct slotsFoo vtableFoo = { // only one constant table
0, // no parent class
destructFoo,
aFoo
};
void constructFoo(Foo* self) { // Foo::Foo()
self->vtable = &vtableFoo; // object points to class vtable
}
void copyConstructFoo(Foo* self,
Foo* other) { // Foo::Foo(const Foo&)
self->vtable = &vtableFoo; // don't copy from other!
}
typedef struct Bar_t { // class Bar
Foo base; // inherit all members of Foo
} Bar;
void destructBar(Bar* self) { } // Bar::~Bar
int aBar(Bar* self) { return 2; } // Bar::a()
const struct slotsFoo vtableBar = { // one more constant table
&vtableFoo, // can dynamic_cast to Foo
(void(*)(Foo*)) destructBar, // must cast type to avoid errors
(int(*)(Foo*)) aBar
};
void constructBar(Bar* self) { // Bar::Bar()
self->base.vtable = &vtableBar; // point to Bar vtable
}
void f(Foo* arg) { // same functionality as above
Foo x; constructFoo(&x); aFoo(&x);
Bar y; constructBar(&y); aBar(&y);
arg->vtable->a(arg); // virtual function call
Foo z; copyConstructFoo(&z, arg);
aFoo(&z);
destructFoo(&z);
destructBar(&y);
destructFoo(&x);
}
因此,您可以看到,vtable只是内存中的静态块,主要包含函数指针。多态类的每个对象都将指向与其动态类型相对应的vtable。这也使RTTI和虚函数之间的联系更加清晰:您可以通过查看类所指向的vtable来检查类的类型。上面以多种方式进行了简化,例如多重继承,但是总体概念是合理的。
如果arg
是类型的Foo*
而您接受了arg->vtable
,但实际上是类型的对象Bar
,那么您仍然得到正确的地址vtable
。这是因为vtable
无论它是在调用vtable
还是base.vtable
在正确键入的表达式中,始终是对象地址的第一个元素。
该答案已合并到社区Wiki答案中
答案是未指定-如果未定义(通常不是),则调用纯虚函数会导致未定义行为(ISO / IEC 14882:2003 10.4-2)。有些实现只是在vtable条目中放置了一个NULL指针。其他实现则将指向虚拟方法的指针放置为执行与声明类似的操作。
请注意,抽象类可以为纯虚函数定义实现,但是该函数只能使用限定ID语法来调用(即,在方法名称中完全指定该类,类似于从a调用基类方法派生类)。这样做是为了提供易于使用的默认实现,同时仍然需要派生类提供重写。
您可以使用函数指针作为类的成员并使用静态函数作为实现,或者使用指向成员函数和实现的成员函数的指针来重新创建C ++中虚拟函数的功能。两种方法之间只有符号上的优势...实际上,虚拟函数调用本身只是符号上的便利。实际上,继承只是一种符号上的方便性...无需使用语言功能即可实现继承。:)
下面是未经测试的废话,可能是错误的代码,但希望可以证明这个想法。
例如
class Foo
{
protected:
void(*)(Foo*) MyFunc;
public:
Foo() { MyFunc = 0; }
void ReplciatedVirtualFunctionCall()
{
MyFunc(*this);
}
...
};
class Bar : public Foo
{
private:
static void impl1(Foo* f)
{
...
}
public:
Bar() { MyFunc = impl1; }
...
};
class Baz : public Foo
{
private:
static void impl2(Foo* f)
{
...
}
public:
Baz() { MyFunc = impl2; }
...
};
void(*)(Foo*) MyFunc;
这是Java语法吗?
我将尝试使其变得简单:)
这是一个带有指向函数的指针的数组,这些函数是特定虚函数的实现。该数组中的索引表示为类定义的虚拟函数的特定索引。这包括纯虚拟功能。
当一个多态类从另一个多态类派生时,我们可能会遇到以下情况:
非标准方式-没有API可以访问它们。编译器可能具有一些扩展程序或专用API来访问它们,但这仅是扩展程序。
只有那些具有至少一个虚函数(甚至是析构函数)或派生至少一个具有其vtable的类(“多态”)的类。
这是可能的实现,但是没有实践。取而代之的是,通常有一个函数会打印出类似“ pure virtual function named”的字样并执行abort()
。如果尝试在构造函数或析构函数中调用abstract方法,则可能会发生对此的调用。
减速仅取决于将呼叫解析为直接呼叫还是虚拟呼叫。没什么关系。:)
如果您通过指针或对对象的引用来调用虚拟函数,则它将始终实现为虚拟调用-因为编译器永远无法在运行时知道将哪种对象分配给该指针,以及该对象是否属于对象。是否重写此方法的类。仅在两种情况下,编译器才能将对虚拟函数的调用解析为直接调用:
final
在您具有指针或引用的类中声明的,您可以通过该指针或引用对其进行调用(仅在C ++ 11中)。在这种情况下,编译器知道此方法无法进行任何进一步的重写,并且只能是此类中的方法。请注意,尽管虚拟调用仅具有取消引用两个指针的开销。使用RTTI(尽管仅适用于多态类)比调用虚拟方法要慢,但如果您找到一种情况来以两种方式实现相同的事情,则使用RTTI较慢。例如,virtual bool HasHoof() { return false; }
仅定义然后覆盖,bool Horse::HasHoof() { return true; }
将为您提供if (anim->HasHoof())
比try更快的调用能力if(dynamic_cast<Horse*>(anim))
。这是因为dynamic_cast
在某些情况下甚至必须递归地遍历类层次结构,以查看是否可以从实际指针类型和所需的类类型构建路径。虽然虚拟调用始终相同-取消引用两个指针。
这是现代C ++中可运行的虚拟表手动实现。它具有定义明确的语义,没有hack,也没有void*
。
注:.*
和->*
是不同的运营商比*
和->
。成员函数指针的工作方式不同。
#include <iostream>
#include <vector>
#include <memory>
struct vtable; // forward declare, we need just name
class animal
{
public:
const std::string& get_name() const { return name; }
// these will be abstract
bool has_tail() const;
bool has_wings() const;
void sound() const;
protected: // we do not want animals to be created directly
animal(const vtable* vtable_ptr, std::string name)
: vtable_ptr(vtable_ptr), name(std::move(name)) { }
private:
friend vtable; // just in case for non-public methods
const vtable* const vtable_ptr;
std::string name;
};
class cat : public animal
{
public:
cat(std::string name);
// functions to bind dynamically
bool has_tail() const { return true; }
bool has_wings() const { return false; }
void sound() const
{
std::cout << get_name() << " does meow\n";
}
};
class dog : public animal
{
public:
dog(std::string name);
// functions to bind dynamically
bool has_tail() const { return true; }
bool has_wings() const { return false; }
void sound() const
{
std::cout << get_name() << " does whoof\n";
}
};
class parrot : public animal
{
public:
parrot(std::string name);
// functions to bind dynamically
bool has_tail() const { return false; }
bool has_wings() const { return true; }
void sound() const
{
std::cout << get_name() << " does crrra\n";
}
};
// now the magic - pointers to member functions!
struct vtable
{
bool (animal::* const has_tail)() const;
bool (animal::* const has_wings)() const;
void (animal::* const sound)() const;
// constructor
vtable (
bool (animal::* const has_tail)() const,
bool (animal::* const has_wings)() const,
void (animal::* const sound)() const
) : has_tail(has_tail), has_wings(has_wings), sound(sound) { }
};
// global vtable objects
const vtable vtable_cat(
static_cast<bool (animal::*)() const>(&cat::has_tail),
static_cast<bool (animal::*)() const>(&cat::has_wings),
static_cast<void (animal::*)() const>(&cat::sound));
const vtable vtable_dog(
static_cast<bool (animal::*)() const>(&dog::has_tail),
static_cast<bool (animal::*)() const>(&dog::has_wings),
static_cast<void (animal::*)() const>(&dog::sound));
const vtable vtable_parrot(
static_cast<bool (animal::*)() const>(&parrot::has_tail),
static_cast<bool (animal::*)() const>(&parrot::has_wings),
static_cast<void (animal::*)() const>(&parrot::sound));
// set vtable pointers in constructors
cat::cat(std::string name) : animal(&vtable_cat, std::move(name)) { }
dog::dog(std::string name) : animal(&vtable_dog, std::move(name)) { }
parrot::parrot(std::string name) : animal(&vtable_parrot, std::move(name)) { }
// implement dynamic dispatch
bool animal::has_tail() const
{
return (this->*(vtable_ptr->has_tail))();
}
bool animal::has_wings() const
{
return (this->*(vtable_ptr->has_wings))();
}
void animal::sound() const
{
(this->*(vtable_ptr->sound))();
}
int main()
{
std::vector<std::unique_ptr<animal>> animals;
animals.push_back(std::make_unique<cat>("grumpy"));
animals.push_back(std::make_unique<cat>("nyan"));
animals.push_back(std::make_unique<dog>("doge"));
animals.push_back(std::make_unique<parrot>("party"));
for (const auto& a : animals)
a->sound();
// note: destructors are not dispatched virtually
}
每个对象都有一个vtable指针,该指针指向成员函数的数组。
除以下问题外,Burly的答案在这里是正确的:
对于至少一个条目的函数指针,抽象类是否仅具有NULL?
答案是根本没有为抽象类创建虚拟表。不需要,因为不能创建这些类的对象!
换句话说,如果我们有:
class B { ~B() = 0; }; // Abstract Base class
class D : public B { ~D() {} }; // Concrete Derived class
D* pD = new D();
B* pB = pD;
通过pB访问的vtbl指针将是类D的vtbl。这正是实现多态的方式。即,如何通过pB访问D方法。B类不需要vtbl。
如果我描述中的B类具有未被D覆盖的虚拟方法foo()和未被覆盖的虚拟方法bar(),则D的vtbl将具有指向B的foo()及其自身的bar()的指针。。仍然没有为B创建vtbl。
B
应该是必要的。仅仅因为其某些方法具有(默认)实现,并不意味着它们必须存储在vtable中。但是,我只是先运行了您的代码(对一些修复程序进行了一些模块化修改,以使其编译),gcc -S
然后在c++filt
其中显然B
包含了一个vtable 。我猜可能是因为vtable还存储了RTTI数据,如类名和继承。可能需要dynamic_cast<B*>
。甚至-fno-rtti
不能使vtable消失。用clang -O3
代替而不是gcc
突然消失了。
我稍早做了一个非常可爱的概念证明(看继承顺序是否重要);让我知道您的C ++实现是否实际上拒绝了它(我的gcc版本仅警告分配匿名结构,但这是一个错误),我很好奇。
CCPolite.h:
#ifndef CCPOLITE_H
#define CCPOLITE_H
/* the vtable or interface */
typedef struct {
void (*Greet)(void *);
void (*Thank)(void *);
} ICCPolite;
/**
* the actual "object" literal as C++ sees it; public variables be here too
* all CPolite objects use(are instances of) this struct's structure.
*/
typedef struct {
ICCPolite *vtbl;
} CPolite;
#endif /* CCPOLITE_H */
CCPolite_constructor.h:
/**
* unconventionally include me after defining OBJECT_NAME to automate
* static(allocation-less) construction.
*
* note: I assume CPOLITE_H is included; since if I use anonymous structs
* for each object, they become incompatible and cause compile time errors
* when trying to do stuff like assign, or pass functions.
* this is similar to how you can't pass void * to windows functions that
* take handles; these handles use anonymous structs to make
* HWND/HANDLE/HINSTANCE/void*/etc not automatically convertible, and
* require a cast.
*/
#ifndef OBJECT_NAME
#error CCPolite> constructor requires object name.
#endif
CPolite OBJECT_NAME = {
&CCPolite_Vtbl
};
/* ensure no global scope pollution */
#undef OBJECT_NAME
main.c:
#include <stdio.h>
#include "CCPolite.h"
// | A Greeter is capable of greeting; nothing else.
struct IGreeter
{
virtual void Greet() = 0;
};
// | A Thanker is capable of thanking; nothing else.
struct IThanker
{
virtual void Thank() = 0;
};
// | A Polite is something that implements both IGreeter and IThanker
// | Note that order of implementation DOES MATTER.
struct IPolite1 : public IGreeter, public IThanker{};
struct IPolite2 : public IThanker, public IGreeter{};
// | implementation if IPolite1; implements IGreeter BEFORE IThanker
struct CPolite1 : public IPolite1
{
void Greet()
{
puts("hello!");
}
void Thank()
{
puts("thank you!");
}
};
// | implementation if IPolite1; implements IThanker BEFORE IGreeter
struct CPolite2 : public IPolite2
{
void Greet()
{
puts("hi!");
}
void Thank()
{
puts("ty!");
}
};
// | imposter Polite's Greet implementation.
static void CCPolite_Greet(void *)
{
puts("HI I AM C!!!!");
}
// | imposter Polite's Thank implementation.
static void CCPolite_Thank(void *)
{
puts("THANK YOU, I AM C!!");
}
// | vtable of the imposter Polite.
ICCPolite CCPolite_Vtbl = {
CCPolite_Thank,
CCPolite_Greet
};
CPolite CCPoliteObj = {
&CCPolite_Vtbl
};
int main(int argc, char **argv)
{
puts("\npart 1");
CPolite1 o1;
o1.Greet();
o1.Thank();
puts("\npart 2");
CPolite2 o2;
o2.Greet();
o2.Thank();
puts("\npart 3");
CPolite1 *not1 = (CPolite1 *)&o2;
CPolite2 *not2 = (CPolite2 *)&o1;
not1->Greet();
not1->Thank();
not2->Greet();
not2->Thank();
puts("\npart 4");
CPolite1 *fake = (CPolite1 *)&CCPoliteObj;
fake->Thank();
fake->Greet();
puts("\npart 5");
CPolite2 *fake2 = (CPolite2 *)fake;
fake2->Thank();
fake2->Greet();
puts("\npart 6");
#define OBJECT_NAME fake3
#include "CCPolite_constructor.h"
fake = (CPolite1 *)&fake3;
fake->Thank();
fake->Greet();
puts("\npart 7");
#define OBJECT_NAME fake4
#include "CCPolite_constructor.h"
fake2 = (CPolite2 *)&fake4;
fake2->Thank();
fake2->Greet();
return 0;
}
输出:
part 1
hello!
thank you!
part 2
hi!
ty!
part 3
ty!
hi!
thank you!
hello!
part 4
HI I AM C!!!!
THANK YOU, I AM C!!
part 5
THANK YOU, I AM C!!
HI I AM C!!!!
part 6
HI I AM C!!!!
THANK YOU, I AM C!!
part 7
THANK YOU, I AM C!!
HI I AM C!!!!
请注意,由于我从不分配伪造的对象,因此无需进行任何销毁;析构函数会自动置于动态分配对象范围的末尾,以回收对象文字本身和vtable指针的内存。
Inside the C++ Object Model
通过Stanley B. Lippman
。(第4.2节,第124-131页)