假设您想要一个用于生成多态对象的类:
class Product{
virtual ~Product(){}
}
class ProductFactory {
virtual const char* getName() = 0;
virtual std::unique_ptr<Product> createProduct() = 0;
}
现在,比如说,给定一个 name
您希望能够创建该名称的产品。 所以我们需要的是一个数据结构,例如,我们程序中所有产品工厂的列表或 vector 。 假设所有工厂将始终是全局对象,因此不是动态生成的,而是在程序启动时全部实例化。让我们进一步说所有工厂构造函数都是constexpr
所以我们可以在运行时实例化它们而无需做任何工作。在单体代码库中,我们可以拥有所有产品的所有工厂的全局数组,然后迭代它们以获得正确的工厂:
// in a .cpp
class AProductFactory {
constexpr AProductFactory(){...}
const char* getName() { return "a" };
std::unique_ptr<Product> createProduct() {...}
}
class BProductFactory {
constexpr BProductFactory(){...}
const char* getName() { return "b" };
std::unique_ptr<Product> createProduct() {...}
}
AProductFactory afac;
BProductFactory bfac;
std::array<ProductFactory*> factories = {afac, bfac};
std::unique_ptr<Product> createProduct(const char* name){
for (auto f: factories) {
if (strcmp(f->getName(),name)==0){
return f->createProduct();
}
}
throw; // Shouldn't happen
}
到目前为止很棒。这将为我们提供所有工厂的列表,而无需在运行时执行任何代码!但这仅适用于单一代码库,其中一个编译单元可以访问所有可能的工厂。但是现在,这对于模块化代码库如何工作,其中可能存在添加新工厂类型的各种编译单元,我们希望避免拥有必须了解所有这些的编译单元。相反,我们只想在所有链接的编译单元中添加所有工厂。当然,我们可以让每个工厂在全局注册表中注册自己:
class FactoryRegistry{
std::vector<ProductFactory*> factories;
void addFactory(ProductFactory* fac){
factories.push_back(fac);
}
static FactoryRegistry& getRegistry() {
static FactoryRegistry fac;
return fac;
}
}
// Now, each factory would call getRegistry().add(this); to register itself at program start-up
但问题是我们现在必须在每个工厂的程序启动上做一点工作,因为每个工厂都必须注册自己。如果我们的代码库中有很多注册表和工厂,这可能会使程序启动速度变慢。 C++ 通常擅长在编译时做尽可能多的工作,但是现在在编译时不再知道该列表(因为没有一个编译单元包含所有工厂),而只是在链接时。那么,有没有办法让链接器完成工作并在链接时创建现有工厂的列表?我们需要一种方法让链接器以某种方式——例如——在链接时创建所有现有工厂的链接列表,因此我们将拥有任何链接编译单元中的所有工厂的列表,而无需做任何工作在运行时。
为了更好地理解我在想什么,这里有一些接近我想要的东西:
struct FactoryChain;
struct ProductFactory {
FactoryChain* chain;
constexpr ProductFactory(FactoryChain* chain) : chain(chain){}
ProductFactory* getNext();
};
struct FactoryChain {
ProductFactory* next;
};
ProductFactory* ProductFactory::getNext() { return chain->next; }
// In ProductFactory4.cpp
extern FactoryChain factory5;
struct ProductFactory4 : ProductFactory {
constexpr ProductFactory4(FactoryChain* chain) : ProductFactory(chain){}
};
ProductFactory4 fac{&factory5};
FactoryChain factory4{&fac}; // Factory 3 may use this
https://godbolt.org/z/79MjTc现在,每个工厂都可以得到下一个工厂,每个工厂只需要知道下一个工厂(通过
extern
声明),所以我们不再需要知道所有工厂的编译单元。现在,链接器将插入 factory5 的地址,因此我们在程序启动时无需做任何工作即可有效地获得所有工厂的列表。查看godbolt链接,程序启动时确实没有生成代码:ProductFactory::getNext(): # @ProductFactory::getNext()
movq (%rdi), %rax
movq (%rax), %rax
retq
fac:
.quad factory5
factory4:
.quad fac
所以我的两个主要要求(程序启动时没有工作,没有需要知道所有工厂的编译单元)得到满足,但是,每个声明工厂的编译单元都需要知道另一个声明工厂的编译单元,这给了我们相当不相关工厂的奇怪依赖。所以唯一缺少的部分是我们仍然需要
extern
下一个工厂的声明。如果我们能以某种方式——使用一些链接器魔法——而不是让链接器给我们下一个工厂的地址,而不必声明其变量的名称,我们就会得到我们想要的。例如(伪代码,肯定不会编译):extern ProductFactory* factory##<Mr Linker or Mr. Pre-processor, please insert next number here>;
以某种方式,这样的事情可能吗?
最佳答案
这种事情当然是可能的,但是因为 C++ 标准没有说明链接器或全局对象的顺序,所以它将依赖于工具链的一些特定于实现的细节(也就是说,可能在 Linux 上的 g++ 和 Windows 上的 g++ 之间移植或g++ 在嵌入式系统上,但不在 Windows 上的 g++ 和 Windows 上的 Microsoft Visual C++ 之间)。
基本方法将适用于许多不同的平台和工具链:
const
(更好的是 constexpr
)以及外部链接——请注意 const
和 constexpr
两者都更改了默认链接;您必须明确标记这些指针 extern
. reinterpret_cast
这个指向公共(public)基类的指针并像一个指向可以多态使用的对象的指针数组一样遍历它。 在 g++ 上,文档提供了以下示例,用于在特定部分中放置全局变量:
int init_data __attribute__ ((section ("INITDATA"))) = 0;
对于 Microsoft Visual C++,概念是相同的,但语法却大不相同:__declspec(allocate("mysec")) int i = 0;
关于c++ - 链接器生成的列表(可能是高度非标准的)C++。可能的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64648968/