vtable能被修改或者在运行时直接访问吗?
vtable是否存在于所有类中,还是只存在于至少有一个虚函数的类中?
抽象类是否仅在至少有一个条目的函数指针上具有NULL值?
是否拥有单个虚函数会减慢整个类的速度?或者只有调用虚函数的速度会减慢?如果虚函数被覆盖,速度会受到影响吗,还是只要它是虚函数就没有影响?
根据《C++中的虚函数》,每当程序有一个声明了虚函数的类时,就会为该类构建一个v-table。 v-table包含包含一个或多个虚函数的类的虚函数地址。 包含一个或多个虚拟函数的类的对象包含一个虚拟指针,该指针指向内存中虚拟表的基地址。 每当有虚拟函数调用时,将使用v-table来解析到函数地址。 含有一个或多个虚拟函数的类的对象在内存中的最开始处有一个叫做vptr的虚拟指针。 因此,在这种情况下,对象的大小增加了指针的大小。此vptr包含虚拟表在内存中的基地址。 请注意,虚拟表是特定于类的,即一个类只有一个虚拟表,无论它包含多少个虚拟函数。 虚拟表反过来包含类的一个或多个虚拟函数的基地址。 在调用对象上的虚拟函数时,该对象的vptr提供该类在内存中的虚拟表的基地址。 此表用于解析函数调用,因为它包含该类的所有虚拟函数的地址。 这就是在虚函数调用期间如何解决动态绑定的过程。
普遍而言,我的回答是“不行”。您可以进行一些内存操作以查找v-table,但仍无法知道函数签名的外观以调用它。可以在没有直接访问或在运行时修改vtable的情况下实现此功能(支持语言)。此外,请注意,C++语言规范不要求 使用vtables - 但这是大多数编译器实现虚函数的方式。
我认为这里的答案是“要看具体实现”,因为规范本身并没有要求使用vtable。但是在实践中,我认为所有现代编译器只有在类中至少有一个虚函数时才会创建vtable。使用虚函数与非虚函数相比存在空间开销和时间开销。
答案是由语言规范未指定,所以取决于具体实现。如果调用纯虚函数但未定义它(通常情况下不定义),则结果是未定义行为(参见ISO/IEC 14882:2003 10.4-2)。实际上,它确实为该函数分配了一个vtable槽位,但没有给它分配地址。这使得vtable不完整,需要派生类来实现该函数并完成vtable。一些实现仅在vtable条目中放置一个NULL指针;其他实现将指针放置到执行某种类似于断言的虚拟函数中。
请注意,抽象类可以为纯虚函数定义一个实现,但只能通过限定符号语法(即,在方法名中完全指定类,类似于从派生类调用基类方法)调用该函数。这样做是为了提供易于使用的默认实现,同时仍要求派生类提供覆盖。
这已经超出了我的知识范围,所以如果我说错了,请有人帮助我!
我认为只有类中的虚函数才会受到与调用非虚函数相比调用虚函数的时间性能影响。类的空间开销无论如何都存在。请注意,如果存在vtable,则每个类仅有1个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()
后置到所有现有实例中。]
在我所见过的大多数编译器中,虚函数表 * 是对象的前4个字节,并且其中的虚函数指针内容只是在那里形成的成员指针数组(通常按照声明顺序排列,基类的在前)。当然,还有其他可能的布局,但这是我通常观察到的情况。
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!
替换所有实例的方法(猴子补丁类)
这个有点棘手,因为虚函数表本身可能在只读内存中。
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位的进程中,它很可能会失败。
***
表示一个指向函数指针的指针。我们将对象的数据重新解释为其 vtbl 的指针(即假设编译器选择将其放在第一位),然后对其进行解引用,并假定 vtbl 由函数指针数组组成,索引元素 [0]
,然后用指向 f3 的指针替换它。这些都不是 C++ 所保证的,但大多数编译器实现的方式通常是如此。研究这个可以满足你对深层次工作原理的好奇心,但不要真的使用这种技术! - puetzk或者只会减慢调用该虚函数的速度?如果虚函数实际上被重写了,速度是否会受到影响,或者只要它是虚函数就没有影响。
拥有虚函数会减慢整个类的速度,因为在处理这样一个类的对象时需要初始化、复制等更多的数据项。对于一个包含半打成员的类,差异应该可以忽略不计。对于只包含单个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
}
我喜欢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);
}
#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
}
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语法吗? - curiousguy我会尽力让它简单易懂 :)
这是一个指向函数的指针数组,这些函数是特定虚函数的实现。该数组中的索引表示类定义的特定虚函数的索引。这包括纯虚函数。
当一个多态类从另一个多态类派生时,我们可能会遇到以下情况:
没有标准方式 - 没有API可以访问它们。编译器可能具有一些扩展或私有API来访问它们,但那可能只是扩展。
只存在于至少有一个虚函数(即使是析构函数)或继承至少一个具有其vtable的类(“是多态的”)的类中。
这是一种可能的实现方式,但不太常用。相反,通常有一个函数打印类似“调用纯虚函数”的内容,并执行abort()
。如果您尝试在构造函数或析构函数中调用抽象方法,则可能会发生这种情况。
减速仅取决于该调用是作为直接调用还是作为虚拟调用解析。其他任何事情都无关紧要。 :)
如果通过指向对象的指针或引用调用虚函数,则它将始终作为虚拟调用实现 - 因为编译器永远无法知道在运行时将分配给此指针的对象类型是什么,以及它是否属于覆盖此方法的类或不属于。只有在两种情况下,编译器才能将调用解析为直接调用:
final
(只有在C++11中)。在这种情况下,编译器知道这个方法不能被进一步重载,它只能是这个类的方法。请注意,虚方法调用只需要两个指针来解除引用的开销。使用RTTI(尽管仅适用于多态类)比调用虚方法更慢,如果您发现有一种情况可以用两种方式实现同样的事情。例如,定义virtual bool HasHoof() { return false; }
,然后仅作为bool Horse::HasHoof() { return true; }
覆盖,将为您提供调用if (anim->HasHoof())
的能力,这将比尝试if(dynamic_cast<Horse*>(anim))
要快。这是因为dynamic_cast
在某些情况下甚至需要递归地遍历类层次结构,以查看是否可以从实际指针类型和所需的类类型构建路径。而虚调用始终相同-解引用两个指针。
这里所有答案中未提到的一点是,如果存在多重继承,其中基类都有虚方法。那么派生类将拥有多个指向虚函数表的指针。 结果是每个此类对象实例的大小都会更大。 众所周知,具有虚方法的类会额外占用4个字节用于虚函数表,但在多重继承的情况下,每个具有虚方法的基类都会额外占用4个字节。4表示指针的大小。