虚拟功能和vtable是如何实现的? 我们都知道C++中的虚函数是什么,但是它们是如何在深层次实现的?
是否可以在运行时修改甚至直接访问vtable vtable是否存在于所有类中,还是仅存在于至少有一个虚函数的类中 抽象类是否至少有一个条目的函数指针为空虚拟功能和vtable是如何实现的? 我们都知道C++中的虚函数是什么,但是它们是如何在深层次实现的?,c++,polymorphism,virtual-functions,vtable,C++,Polymorphism,Virtual Functions,Vtable,是否可以在运行时修改甚至直接访问vtable vtable是否存在于所有类中,还是仅存在于至少有一个虚函数的类中 抽象类是否至少有一个条目的函数指针为空 只有一个虚函数会降低整个类的速度吗?还是只调用虚函数?如果虚拟函数实际被覆盖或未被覆盖,速度是否会受到影响?或者,只要它是虚拟函数,速度是否会受到影响。通常使用VTable,即指向函数的指针数组 每个对象都有一个指向成员函数数组的vtable指针。如何在深层实现虚拟函数? 发件人: 每当一个程序声明了一个虚函数时,就会为该类构造一个v表。v表由
只有一个虚函数会降低整个类的速度吗?还是只调用虚函数?如果虚拟函数实际被覆盖或未被覆盖,速度是否会受到影响?或者,只要它是虚拟函数,速度是否会受到影响。通常使用VTable,即指向函数的指针数组 每个对象都有一个指向成员函数数组的vtable指针。如何在深层实现虚拟函数? 发件人: 每当一个程序声明了一个虚函数时,就会为该类构造一个v表。v表由包含一个或多个虚拟函数的类的虚拟函数地址组成。包含虚拟函数的类的对象包含一个虚拟指针,该指针指向内存中虚拟表的基址。每当有一个虚拟函数调用时,v表就被用来解析函数地址。包含一个或多个虚拟函数的类的对象在内存中对象的最开始处包含一个名为vptr的虚拟指针。因此,在这种情况下,对象的大小随着指针的大小而增加。此vptr包含内存中虚拟表的基址。请注意,虚拟表是特定于类的,即一个类只有一个虚拟表,而与它包含的虚拟函数数无关。该虚拟表依次包含该类的一个或多个虚拟函数的基址。在对对象调用虚拟函数时,该对象的vptr为内存中的该类提供虚拟表的基址。此表用于解析函数调用,因为它包含该类的所有虚拟函数的地址。这就是在虚拟函数调用期间解析动态绑定的方式 是否可以在运行时修改甚至直接访问vtable? 我普遍认为答案是“不”。您可以进行一些内存破坏来查找vtable,但仍然不知道调用它的函数签名是什么样子。如果不直接访问vtable或在运行时修改vtable,那么您希望通过此功能(语言支持)实现的任何事情都应该是可能的。还要注意,C++语言规范>强>不< /强>指定VTHOST是必需的,但是这就是大多数编译器实现虚拟函数的方式。 vtable是否存在于所有对象中,还是仅存在于至少具有一个虚拟功能的对象中? 我相信这里的答案是“这取决于实现”,因为规范一开始并不需要vtables。然而,在实践中,我相信所有现代编译器只有在一个类至少有一个虚函数的情况下才能创建vtable。与vtable相关联的是空间开销,与调用虚拟函数和非虚拟函数相关联的是时间开销 抽象类是否至少有一个条目的函数指针为空? 答案是它没有被语言规范指定,所以它取决于实现。调用纯虚函数会导致未定义的行为(通常未定义)(ISO/IEC 14882:2003 10.4-2)。实际上,它确实在vtable中为函数分配了一个插槽,但没有为其分配地址。这使得vtable不完整,这需要派生类来实现函数并完成vtable。有些实现只是在vtable条目中放置一个空指针;其他实现将指针放在执行类似于断言的操作的伪方法上 请注意,抽象类可以定义纯虚函数的实现,但该函数只能使用限定id语法调用(即,在方法名称中完全指定类,类似于从派生类调用基类方法)。这样做是为了提供易于使用的默认实现,同时仍然要求派生类提供重写 拥有一个虚拟函数会降低整个类的速度,还是只会降低对虚拟函数的调用速度? 这已经到了我知识的边缘,如果我错了,请有人来帮我 我相信只有类中的虚拟函数才会经历与调用虚拟函数和非虚拟函数相关的时间性能影响。无论哪种方式,类的空间开销都是存在的。请注意,如果有vtable,则每个类只有一个vtable,而不是每个对象一个vtable 如果虚拟函数实际上被重写或未被重写,速度是否会受到影响,或者只要它是虚拟函数,速度是否就没有影响? 我不认为被重写的虚拟函数的执行时间比调用基本虚拟函数要短。但是,与为派生类定义另一个vtable而不是基类相关联的类会有额外的空间开销 额外资源: (通过回程机)
此答案已包含在
- 抽象类是否至少有一个条目的函数指针有空值?
class B { ~B() = 0; }; // Abstract Base class
class D : public B { ~D() {} }; // Concrete Derived class
D* pD = new D();
B* pB = pD;
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
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; }
...
};
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
}
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);
}
#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 */
/**
* 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
#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!!!!
#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
}