问题描述
注意:这个问题纯粹是关于 asm.js
,不是关于C ++或其他程式语言。
标题已经说过:
如何以有效的方式实现函数指针?
我在网上找不到任何东西,所以我想在这里问。
编辑:
我想在我正在编译的编译器中实现虚函数。
在C ++中,我会这样做,生成一个 / code>:
#include< iostream>
class Base {
public:
virtual void doSomething()= 0;
};
class Derived:public Base {
public:
void doSomething(){
std :: cout< 我在做什么...<< std :: endl;
}
};
int main()
{
Base * instance = new Derived();
instance-> doSomething();
return 0;
}
如何在asm.js中生成 vtable
,而不需要纯JavaScript?
在任何情况下,我想使用asm.js的near native功能,同时使用函数指针。
该解决方案可能适合计算机生成的代码
查看asm.js的工作原理,我相信您最好的选择是使用原始CFront编译器使用的方法:将virtual方法向下编译为接受this指针的函数,并在传递this指针之前使用thunk来修正this指针。
减少方法到特殊函数:
void ExampleObject :: foo(void);
将转换为
void exampleobject_foo(ExampleObject * this);
这对于基于非继承的对象非常有用。
单一继承
我们可以轻松地添加对任意大量单继承通过一个简单的技巧:总是存储对象在内存基地首先:
类A:public B
会在内存中变为:
[[B] A]
接近!
多重继承
现在,多重继承使得使用起来更加困难。
class A:public B,public C
B和C都不可能在A开始;它们根本不能共存。有两种选择:
- 为每次基准调用存储显式偏移量(称为增量)。
- 不允许通过A到B或C的呼叫
第二种选择由于种种原因而优先;如果你正在调用一个基类成员函数,很少有人想通过派生类来做。相反,你可以简单地去C :: conlyfunc,然后可以做调整你的指针为你免费。允许A :: conlyfunc删除编译器可以使用的重要信息,但很少受益。
第一个选择在C ++中使用;所有多重继承对象在每次调用基类之前调用thunk,它将this指针调整为指向其中的子对象。在一个简单的例子中:
class ExampleBaseClass
{
void foo(void);
}
class ExampleDerivedClass:public ExampleBaseClass,private IrrelevantBaseClass
{
void bar(void);
}
会成为
void examplebaseclass_foo(ExampleBaseClass * this);
void examplederivedclass_bar(ExampleDerivedClass * this);
void examplederivedclass_thunk_foo(ExampleDerivedClass * this)
{
examplebaseclass_foo(this + delta);
}
这在许多情况下都可以内联,所以它不是太大的开销。但是,如果你不能将ExampleBaseClass :: foo引用为ExampleDerivedClass :: foo,则不需要这些thunk,因为delta将很容易从调用本身识别。
虚拟功能
虚拟功能增加了一个全新的复杂层。在多继承的例子中,thunk有固定的地址调用;我们只是调整这个,然后传递给一个已知的函数。使用虚函数,我们调用的函数是未知的;我们可以被一个派生对象覆盖,我们在编译时不可能知道,因为它在另一个翻译单元或库等。
这意味着我们需要某种形式的动态分派每个对象有一个虚拟可重写的功能;许多方法是可能的,但是C ++实现往往使用一个简单的函数指针数组或者一个vtable。对于每个具有虚函数的对象,我们通常在前面添加一个指向数组的隐藏成员:
A
{
hidden:
void * vtable;
public:
virtual void foo(void);
}
我们添加了重定向到vtable的thunk函数
void a_foo(A * this)
{
int vindex = 0;
this-> vtable [vindex](this);
}
然后使用指向我们实际想调用的函数的指针填充vtable:
vtable [0] =& A :: foo_default; //我们的基础类foo
在派生类中,如果我们希望覆盖这个虚函数,我们需要做的就是改变自己对象中的vtable,指向新的函数,它将在基类中重写:
class B:public A
{
virtual void foo(void);
}
会在构造函数中执行此操作:
((A *)this) - > vtable [0] =& B :: foo;
最后,我们支持所有形式的继承!
几乎。
虚拟继承
此实现有一个最后警告:如果继续允许Derived: :foo当你真正的意思是使用Base :: foo,你会得到钻石问题:
class A:public B,public C;
B类:public D;
class C:public D;
A :: DFunc(); //哪个D?
当你使用基类作为有状态类时,具有-a而不是is-a;一般来说,这是一个需要重组的标志。但不总是。
在C ++中,这个解决方案不是非常优雅,但是工作原理:
class A:public B,public C;
class B:virtual D;
class C:virtual D;
这需要那些实现这样的类和层次结构的人前进,故意使他们的类慢一些,以支持可能的未来使用。但它确实能解决问题。
我们如何实现这个解决方案?
[[D] [B] [Dptr] [C] [Dptr] A]
而不是直接使用基类,如在正常继承中,通过虚拟继承,我们通过一个指针,添加一个间接,同时把多个实例化成一个单一的推送所有的D使用。注意,B和C都有自己的指针,都指向同一个D;这是因为B和C不知道它们是自由浮动副本还是绑定在派生对象中。
摘要
- 将方法调用转换为基类中具有特殊此参数的函数调用
- 在内存中结构对象,所以单继承没有不同从没有继承
- 添加thunk调整这个指针,然后调用基类以进行多重继承
- 使用虚拟方法将vtables添加到类中,方法通过vtable到方法(thunk - > vtable - > method)
- 通过指针到基础对象而不是派生对象调用来处理虚拟继承
所有这些在js.asm中都很简单。
Note: This question is purely about asm.js
not about C++ nor any other programming language.
As the title already says:
How should a function pointer be implemented in a efficient way?
I couldn't find anything on the web, so I figured asking it here.
Edit:I would like to implement virtual functions in the compiler I'm working on.
In C++ I would do something like this to generate a vtable
:
#include <iostream>
class Base {
public:
virtual void doSomething() = 0;
};
class Derived : public Base {
public:
void doSomething() {
std::cout << "I'm doing something..." << std::endl;
}
};
int main()
{
Base* instance = new Derived();
instance->doSomething();
return 0;
}
To be more precise; how can I generate a vtable
in asm.js without the need of plain JavaScript?In any case, I would like the "near native" capabilities of asm.js while using function pointers.
The solution may be suitable for computer generated code only.
解决方案 Looking over how asm.js works, I believe your best bet would be to use the method the original CFront compiler used: compile the virtual methods down to functions that take a this pointer, and use thunks to correct the this pointer before passing it. I'll go through it step by step:
No Inheritance
Reduce methods to special functions:
void ExampleObject::foo( void );
would be transformed into
void exampleobject_foo( ExampleObject* this );
This works fine for non-inheritance based objects.
Single Inheritance
We can easily add support for arbitrary large amount of single inheritance through a simple trick: always store the object in memory base first:
class A : public B
would become, in memory:
[[ B ] A ]
Getting closer!
Multiple InheritanceNow, multiple inheritance makes this much harder to work with
class A : public B, public C
It's impossible for both B and C to be at the start of A; they simply cannot co-exist. There are two choices:
- Store an explicit offset (known as delta) for each call to base.
- Do not allow calls through A to B or C
The second choice is much preferable for a variety of reasons; if you are calling a base class member function, it's rare you would want to do it through a derived class. Instead, you could simply go C::conlyfunc, which could then do the adjustment to your pointer for you at no cost. Allowing A::conlyfunc removes important information that the compiler could have used, at very little benefit.
The first choice is used in C++; all multiple inheritance objects call a thunk before each call to a base class, which adjusts the this pointer to point to the subobject inside it. In a simple example:
class ExampleBaseClass
{
void foo( void );
}
class ExampleDerivedClass : public ExampleBaseClass, private IrrelevantBaseClass
{
void bar( void );
}
would then become
void examplebaseclass_foo( ExampleBaseClass* this );
void examplederivedclass_bar( ExampleDerivedClass* this);
void examplederivedclass_thunk_foo( ExampleDerivedClass* this)
{
examplebaseclass_foo( this + delta );
}
This could be inlined in many situations, so it's not too big of overhead. However, if you could never refer to ExampleBaseClass::foo as ExampleDerivedClass::foo, these thunks wouldn't be needed, as the delta would be easily discernible from the call itself.
Virtual functions
Virtual functions adds a whole new layer of complexity. In the multiple inheritance example, the thunks had fixed addresses to call; we were just adjusting the this before passing it to an already known function. With virtual functions, the function we're calling is unknown; we could be overridden by a derived object we have no possibility of knowing about at compile time, due to it being in another translation unit or a library, etc.
This means we need some form of dynamic dispatch for each object that has a virtually overridable function; many methods are possible, but C++ implementations tend to use a simple array of function pointers, or a vtable. To each object that has virtual functions, we add a point to an array as a hidden member, usually at the front:
class A
{
hidden:
void* vtable;
public:
virtual void foo( void );
}
We add thunk functions which redirect to the vtable
void a_foo( A* this )
{
int vindex = 0;
this->vtable[vindex](this);
}
The vtable is then populated with pointers to the functions we actually want to call:
vtable[0] = &A::foo_default; // our baseclass implimentation of foo
In a derived class, if we wish to override this virtual function, all we need to do is change the vtable in our own object, to point to the new function, and it will override in the base class as well:
class B: public A
{
virtual void foo( void );
}
will then do this in the constructor:
((A*)this)->vtable[0] = &B::foo;
Finally, we have support for all forms of inheritance!Almost.
Virtual Inheritance There is one final caveat with this implementation: if you continue to allow Derived::foo to be used when what you really mean is Base::foo, you get the diamond problem:
class A : public B, public C;
class B : public D;
class C : public D;
A::DFunc(); // Which D?
This problem can also occur when you use base classes as stateful classes, or when you put function that should be has-a rather than is-a; generally, it's a sign of a need for a restructure. But not always.
In C++, this has a solution that is not very elegant, but works:
class A : public B, public C;
class B : virtual D;
class C : virtual D;
This requires those who implement such classes and hierarchies to think ahead and intentionally make their classes a little slower, to support a possible future usage. But it does solve the problem.
How can we implement this solution?
[ [ D ] [ B ] [ Dptr ] [ C ] [ Dptr ] A ]
Rather than use the base class directly, as in normal inheritance, with virtual inheritance we push all usages of D through a pointer, adding an indirection, while stomping the multiple instantiations into a single one. Notice that both B and C have their own pointer, that both point to the same D; this is because B and C don't know if they are free floating copies or bound in derived objects. The same calls need to be used for both, or virtual functions won't work as expected.
Summary
- Transform method calls into function calls with a special this parameter in base classes
- Structure objects in memory so single inheritance is no different from no inheritance
- Add thunks to adjust this pointers then call base classes for multiple inheritance
- Add vtables to classes with virtual methods, and make all calls to methods go through vtable to method (thunk -> vtable -> method)
- Deal with virtual inheritance through a pointer-to-baseobject rather than derive object calls
All of this is straightforward in js.asm.
这篇关于asm.js - 如何实现函数指针的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!