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() 函数我看到的是这个。
- dlopen 创建句柄。
- 已创建函数指针,以便三角形 class 可以创建新的三角形对象并销毁它。 (dlopen 为我们提供了一个可以分支到的内存位置。)
- create_triangle() returns 一个三角形被转换成一个多边形(因为我们知道多边形的方法。
- 我们使用基础 class 的 set_side_length 方法设置内部成员 side_length_。
这里是问题:
当调用 poly->area() 时,如何在三角形对象中找到它?
- 我们知道基础 class 在内存中的什么地方有它的 "virtual area()" 方法。
- 由于 "triangle.so" 是动态加载的,编译器无法说我识别出三角形区域 () 正在覆盖程序中的多边形区域 ()。
- 此时名称已完全损坏,.so 可以用 clang++ 编译,程序可以用 g++ 编译。所以目前可能没有希望认出他们。
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
) 析构函数 protected 和 non-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()
.
在 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() 函数我看到的是这个。
- dlopen 创建句柄。
- 已创建函数指针,以便三角形 class 可以创建新的三角形对象并销毁它。 (dlopen 为我们提供了一个可以分支到的内存位置。)
- create_triangle() returns 一个三角形被转换成一个多边形(因为我们知道多边形的方法。
- 我们使用基础 class 的 set_side_length 方法设置内部成员 side_length_。
这里是问题:
当调用 poly->area() 时,如何在三角形对象中找到它?
- 我们知道基础 class 在内存中的什么地方有它的 "virtual area()" 方法。
- 由于 "triangle.so" 是动态加载的,编译器无法说我识别出三角形区域 () 正在覆盖程序中的多边形区域 ()。
- 此时名称已完全损坏,.so 可以用 clang++ 编译,程序可以用 g++ 编译。所以目前可能没有希望认出他们。
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
) 析构函数 protected 和 non-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()
.