dlopen、工厂模式和虚拟方法 table

dlopen, factory pattern and the virtual method table

在 C++ 中使用 dlopen 时,我正在努力思考工厂模式在内部是如何工作的。很抱歉 post。

tl;博士;问题在下面的粗体中。

来自 http://www.tldp.org/HOWTO/C++-dlopen/thesolution.html 的片段删除了错误检查以保存 space:

main.cpp

#include "polygon.hpp"
#include <iostream>
#include <dlfcn.h>

int main()
{
    using std::cout;

    // load the triangle library
    void* triangle = dlopen("./triangle.so", RTLD_LAZY);

    // load the symbols
    // create function pointers
    // (Exposed with extern "C")
    polygon* create_triangle = (polygon*) dlsym(triangle, "create"); 
    void* destroy_triangle = (void*) dlsym(triangle, "destroy");

    // create an instance of the class
    polygon* poly = create_triangle();

    // use the class
    poly->set_side_length(7);
    cout << "The area is: " << poly->area() << '\n';

    destroy_triangle(poly); // destroy the class

    dlclose(triangle);     // unload the triangle library

}

polygon.hpp

#ifndef POLYGON_HPP
#define POLYGON_HPP

class polygon
{
protected:
    double side_length_;

public:
    polygon()
        : side_length_(0) {}

    virtual ~polygon() {}

    void set_side_length(double side_length)
    {
        side_length_ = side_length;
    }

    virtual double area() const = 0;
};

// the types of the class factories
typedef polygon* create_t();
typedef void destroy_t(polygon*);

#endif

triangle.hpp

#include "polygon.hpp"
#include <cmath>

class triangle : public polygon
{
public:
    virtual double area() const
    {
        return side_length_ * side_length_ * sqrt(3) / 2;
    }
};


// the class factories

extern "C" polygon* create()
{
    return new triangle;
}

extern "C" void destroy(polygon* p)
{
    delete p;
}

所以看看 main() 函数我看到的是这个。

  1. dlopen 创建句柄。
  2. 已创建函数指针,以便三角形 class 可以创建新的三角形对象并销毁它。 (dlopen 为我们提供了一个可以分支到的内存位置。)
  3. create_triangle() returns 一个三角形被转换成一个多边形(因为我们知道多边形的方法。
  4. 我们使用基础 class 的 set_side_length 方法设置内部成员 side_length_。

这里是问题:

当调用 poly->area() 时,如何在三角形对象中找到它?

Virtual Member Table 是否根据虚拟方法出现的时间来保持它们的顺序?那么这段代码会破坏它吗?

polygon.hpp

...
virtual double area() const = 0;
virtual double parameter() const = 0;
...

triangle.hpp

...
double parameter() const { ... } // implementing and defining parameter first
area() const { ... } // implementing and defining second.
...

我知道你想让它们保持有序...但是假设我们再 subclass 几次,它们的定义顺序不同...

如果对此有任何帮助,那就太好了。我只是无法想象这里的内存中发生了什么才能使它真正起作用。

谢谢!很抱歉 post.

When poly->area() is called how is this found in the triangle object?

poly 的所有初始化都发生在库内部(包括设置 vptr)。调用者(即可执行文件)唯一要做的就是从特定索引处的 poly 的 vtable 加载虚拟方法指针。可执行文件和 shlib 都共享 poly 的 class 的声明,因此它们都同意哪个 vtable 元素对应于 area.

请注意,编译器不需要知道 任何关于 poly 的实现如何重载基 class.

的任何事情

the .so could have been compiled with clang++ and the program could have been compiled with g++

Clang 很难与 Linux 平台上的 GCC ABI-compatible(Windows 平台上的 Visual Studio 实现这种互操作性。

I know you would want to keep them in order... but let's say we subclass a couple more times and they get defined in a different order...

Subclassing 不会改变基础 class vtable 的结构,它在这一点上是固定的(否则多态性也会破坏)。

请注意,为了增加 inter-compiler 兼容性(以及安全性),我建议使接口 (polygon) 析构函数 protectednon-virtual.

为什么要保护

一般来说,dll 不一定需要与使用它的应用程序具有相同的堆(在不同编译器之间使用时尤其如此),因此您应该禁止在接口上调用 delete(这也是您提供 destroy() 函数的原因)。

为什么non-virtual

这只是出于技术原因,因为虚拟析构函数在不同的编译器下经常以不同的方式实现。一个例子是 Windows 下 MSVC 和 MinGW (GCC) 之间的不兼容 - MSVC 为虚拟析构函数创建一个虚拟 table 条目,而 GCC 为它创建 2 个条目。这使得虚拟 table 移动了一项。对于(内联平凡)non-virtual 析构函数,析构函数的虚拟 table 中没有条目。


此外,我通常不提供额外的 destroy() 函数,而是在接口本身中提供纯虚拟 destroy() 方法(impl 仅调用 delete this;)。可选地使用内联包装器,它测试接口指针是否不为 NULL(因为显然在 NULL 指针上调用虚拟方法将不起作用)。在最好的情况下,可选地包装在一个智能指针中,它实际上负责调用 obj->destroy().