在 C++ 中为模板的所有实例生成单个机器代码实例:是否可行 and/or 可能?
Single machine code instance generated for all instances of a template in C++: Is it feasible and/or possible?
我一直在考虑这个问题,我想我在某处读到过,但我找不到更多关于这个的信息。
仅出于理论而非实际目的,是否可能、可行、and/or 实际生成单个 模板实例,能够处理所有实例这样的模板?有没有编译器能够做到这一点?
例如,考虑这个 class 模板...
template<typename T>
class UselessCalculator {
private:
T result;
public:
UselessCalculator() : result(0) {}
UselessCalculator &operator=(T what) {
this->result = what;
return *this;
}
UselessCalculator &operator+=(T what) {
this->result += what;
return *this;
}
UselessCalculator &operator-=(T what) {
this->result -= what;
return *this;
}
UselessCalculator &operator*=(T what) {
this->result *= what;
return *this;
}
UselessCalculator &operator/=(T what) {
this->result /= what;
return *this;
}
};
对于任何 T
,UselessCalculator
都有一些要求列表,可以用作它的模板参数,就像概念一样。在这种情况下,要求是:
- 可通过
T(0)
初始化。
- 已超载
operator+=(T, T)
。
- 已超载
operator-=(T, T)
。
- 已超载
operator*=(T, T)
。
- 已超载
operator/=(T, T)
。
现在,按照这个愚蠢的 "single instance fits all" 想法,这将如何 实施 ?我认为下面的 C 代码可能说明了一种可能性...
struct UselessCalculatorTemplateVirtualTable {
struct someFunkyImplementationOfStdTypeInfo *type;
void (*constructInt)(void*, int);
void (*copyConstruct)(void*, const void*);
void (*moveConstruct)(void*, void*);
void (*destruct)(void*);
void (*operatorAddAssign)(void*, const void*);
void (*operatorSubtractAssign)(void*, const void*);
void (*operatorMultiplyAssign)(void*, const void*);
void (*operatorDivideAssign)(void*, const void*);
};
// I won't repeat that long name all over the place...
typedef struct UselessCalculatorTemplateVirtualTable VirtualTable;
void UselessCalculatorConstruct(VirtualTable *table, void *this) {
table->constructInt(this, 0);
}
void UselessCalculatorCopy(VirtualTable *table, void *this, const void *what) {
table->copyConstruct(this, what);
}
void UselessCalculatorMove(VirtualTable *table, void *this, void *what) {
table->moveConstruct(this, what);
}
void UselessCalculatorDestruct(VirtualTable *table, void *this) {
table->destruct(this);
}
void UselessCalculatorAddAssign(VirtualTable *table, void *this, void *what) {
table->operatorAddAssign(this, what);
}
void UselessCalculatorAddAssign(VirtualTable *table, void *this, void *what) {
table->operatorSubtractAssign(this, what);
}
void UselessCalculatorMultiplyAssign(VirtualTable *table, void *this, void *what) {
table->operatorMultiplyAssign(this, what);
}
void UselessCalculatorDivideAssign(VirtualTable *table, void *this, void *what) {
table->operatorDivideAssign(this, what);
}
现在,编译器必须为每个 UselessCalculator<T>
"instantiate" 做的唯一事情是 VirtualTable
和辅助函数(如果有的话)。例如,UselessCalculator<int>
将转换为...
#define real(what) ((int*)what)
void constructInt(void *this, int what) {
*real(this) = what;
}
void copyConstruct(void *this, const void *what) {
*real(this) = *real(what);
}
void moveConstruct(void *this, void *what) {
*real(this) = *real(what);
}
void destruct(void *this) {}
void operatorAddAssign(void *this, const void *what) {
*real(this) += *real(what);
}
void operatorSubtractAssign(void *this, const void *what) {
*real(this) -= *real(what);
}
void operatorMultiplyAssign(void *this, const void *what) {
*real(this) *= *real(what);
}
void operatorDivideAssign(void *this, const void *what) {
*real(this) /= *real(what);
}
然后,考虑到这一点...
int main() {
UselessCalculator<int> myUselessCalc;
myUselessCalc += 10;
myUselessCalc *= 10;
myUselessCalc -= 10;
myUselessCalc /= 10;
}
VirtualTable virtualTableInt = {
&someFunkyImplementationOfStdTypeInfoForInt,
constructInt,
copyConstruct,
moveConstruct,
destroy,
addAssign,
subtractAssign,
multiplyAssign,
divideAssign
};
可以翻译成这个 C 代码...(不考虑例外情况!)
struct UselessCalculatorInt {
int result;
};
int main() {
int tmpStorage;
UselessCalculatorInt myUselessCalc;
UselessCalculatorConstruct(&virtualTableInt, &myUselessCalc);
tmpStorage = 10;
UselessCalculatorAddAssign(&virtualTableInt, &myUselessCalc, &tmpStorage);
tmpStorage = 10;
UselessCalculatorSubtractAssign(&virtualTableInt, &myUselessCalc, &tmpStorage);
tmpStorage = 10;
UselessCalculatorMultiplyAssign(&virtualTableInt, &myUselessCalc, &tmpStorage);
tmpStorage = 10;
UselessCalculatorDivideAssign(&virtualTableInt, &myUselessCalc, &tmpStorage);
UselessCalculatorDestroy(&virtualTableInt, &myUselessCalc);
return 0;
}
我知道这会打败模板的所有用途,很多人不会喜欢这个想法(我不喜欢,我只是好奇),而且代码可能效率较低, CPU- 和内存方面,更不用说它几乎是不可优化的,它们将不再是 "templates"。但是其他人 ab 过去用得更糟,不是吗? ;). 顺便说一句,最好有一个开关来禁用它,如果曾经实施过,只要有必要,因为模板元编程将变得几乎无用。
所以,我的问题是,如果还不够清楚,所有这些乱七八糟的东西是否可行、实用、可实施?是否有一些工具链成功地做到了这一点?它会提供任何可以超过其明显开销的好处吗?
好吧,您基本上是在问是否某些编译器可以通过 运行 时间多态性("classic" OOP with virtual functions)实现通常称为 "compile-time polymorphism"(C++ 模板)的东西。
虽然这在理论上可能是可行的(至少在某种程度上是有限的),但如果真的违背了语言设计原则。在编译时多态性更合适的情况下,专门提供模板作为 运行 时多态性的有效替代。模板的全部目的是不同于 "ordinary" 运行 时间多态性。许多模板功能严重依赖于它们的编译时性质。
想法是,如果您使用模板,则意味着您想要编译时多态性,而不是运行时多态性。
这是奖章的一面。另一方面,编写不当的模板代码会导致不必要的代码膨胀,因为在 运行 时间多态性很容易实现相同目标的情况下,会强制执行编译时多态性(即相同代码的重复实例化)更少的代码膨胀和可忽略的性能损失。从这个角度来看,隐式自动切换到模板代码中的 运行 时间多态性可能是有益的。但我相信当前状态下的语言并不是为此量身定做的。这是您应该明确自己做的事情。
我一直在考虑这个问题,我想我在某处读到过,但我找不到更多关于这个的信息。
仅出于理论而非实际目的,是否可能、可行、and/or 实际生成单个 模板实例,能够处理所有实例这样的模板?有没有编译器能够做到这一点?
例如,考虑这个 class 模板...
template<typename T>
class UselessCalculator {
private:
T result;
public:
UselessCalculator() : result(0) {}
UselessCalculator &operator=(T what) {
this->result = what;
return *this;
}
UselessCalculator &operator+=(T what) {
this->result += what;
return *this;
}
UselessCalculator &operator-=(T what) {
this->result -= what;
return *this;
}
UselessCalculator &operator*=(T what) {
this->result *= what;
return *this;
}
UselessCalculator &operator/=(T what) {
this->result /= what;
return *this;
}
};
对于任何 T
,UselessCalculator
都有一些要求列表,可以用作它的模板参数,就像概念一样。在这种情况下,要求是:
- 可通过
T(0)
初始化。 - 已超载
operator+=(T, T)
。 - 已超载
operator-=(T, T)
。 - 已超载
operator*=(T, T)
。 - 已超载
operator/=(T, T)
。
现在,按照这个愚蠢的 "single instance fits all" 想法,这将如何 实施 ?我认为下面的 C 代码可能说明了一种可能性...
struct UselessCalculatorTemplateVirtualTable {
struct someFunkyImplementationOfStdTypeInfo *type;
void (*constructInt)(void*, int);
void (*copyConstruct)(void*, const void*);
void (*moveConstruct)(void*, void*);
void (*destruct)(void*);
void (*operatorAddAssign)(void*, const void*);
void (*operatorSubtractAssign)(void*, const void*);
void (*operatorMultiplyAssign)(void*, const void*);
void (*operatorDivideAssign)(void*, const void*);
};
// I won't repeat that long name all over the place...
typedef struct UselessCalculatorTemplateVirtualTable VirtualTable;
void UselessCalculatorConstruct(VirtualTable *table, void *this) {
table->constructInt(this, 0);
}
void UselessCalculatorCopy(VirtualTable *table, void *this, const void *what) {
table->copyConstruct(this, what);
}
void UselessCalculatorMove(VirtualTable *table, void *this, void *what) {
table->moveConstruct(this, what);
}
void UselessCalculatorDestruct(VirtualTable *table, void *this) {
table->destruct(this);
}
void UselessCalculatorAddAssign(VirtualTable *table, void *this, void *what) {
table->operatorAddAssign(this, what);
}
void UselessCalculatorAddAssign(VirtualTable *table, void *this, void *what) {
table->operatorSubtractAssign(this, what);
}
void UselessCalculatorMultiplyAssign(VirtualTable *table, void *this, void *what) {
table->operatorMultiplyAssign(this, what);
}
void UselessCalculatorDivideAssign(VirtualTable *table, void *this, void *what) {
table->operatorDivideAssign(this, what);
}
现在,编译器必须为每个 UselessCalculator<T>
"instantiate" 做的唯一事情是 VirtualTable
和辅助函数(如果有的话)。例如,UselessCalculator<int>
将转换为...
#define real(what) ((int*)what)
void constructInt(void *this, int what) {
*real(this) = what;
}
void copyConstruct(void *this, const void *what) {
*real(this) = *real(what);
}
void moveConstruct(void *this, void *what) {
*real(this) = *real(what);
}
void destruct(void *this) {}
void operatorAddAssign(void *this, const void *what) {
*real(this) += *real(what);
}
void operatorSubtractAssign(void *this, const void *what) {
*real(this) -= *real(what);
}
void operatorMultiplyAssign(void *this, const void *what) {
*real(this) *= *real(what);
}
void operatorDivideAssign(void *this, const void *what) {
*real(this) /= *real(what);
}
然后,考虑到这一点...
int main() {
UselessCalculator<int> myUselessCalc;
myUselessCalc += 10;
myUselessCalc *= 10;
myUselessCalc -= 10;
myUselessCalc /= 10;
}
VirtualTable virtualTableInt = {
&someFunkyImplementationOfStdTypeInfoForInt,
constructInt,
copyConstruct,
moveConstruct,
destroy,
addAssign,
subtractAssign,
multiplyAssign,
divideAssign
};
可以翻译成这个 C 代码...(不考虑例外情况!)
struct UselessCalculatorInt {
int result;
};
int main() {
int tmpStorage;
UselessCalculatorInt myUselessCalc;
UselessCalculatorConstruct(&virtualTableInt, &myUselessCalc);
tmpStorage = 10;
UselessCalculatorAddAssign(&virtualTableInt, &myUselessCalc, &tmpStorage);
tmpStorage = 10;
UselessCalculatorSubtractAssign(&virtualTableInt, &myUselessCalc, &tmpStorage);
tmpStorage = 10;
UselessCalculatorMultiplyAssign(&virtualTableInt, &myUselessCalc, &tmpStorage);
tmpStorage = 10;
UselessCalculatorDivideAssign(&virtualTableInt, &myUselessCalc, &tmpStorage);
UselessCalculatorDestroy(&virtualTableInt, &myUselessCalc);
return 0;
}
我知道这会打败模板的所有用途,很多人不会喜欢这个想法(我不喜欢,我只是好奇),而且代码可能效率较低, CPU- 和内存方面,更不用说它几乎是不可优化的,它们将不再是 "templates"。但是其他人 ab 过去用得更糟,不是吗? ;). 顺便说一句,最好有一个开关来禁用它,如果曾经实施过,只要有必要,因为模板元编程将变得几乎无用。
所以,我的问题是,如果还不够清楚,所有这些乱七八糟的东西是否可行、实用、可实施?是否有一些工具链成功地做到了这一点?它会提供任何可以超过其明显开销的好处吗?
好吧,您基本上是在问是否某些编译器可以通过 运行 时间多态性("classic" OOP with virtual functions)实现通常称为 "compile-time polymorphism"(C++ 模板)的东西。
虽然这在理论上可能是可行的(至少在某种程度上是有限的),但如果真的违背了语言设计原则。在编译时多态性更合适的情况下,专门提供模板作为 运行 时多态性的有效替代。模板的全部目的是不同于 "ordinary" 运行 时间多态性。许多模板功能严重依赖于它们的编译时性质。
想法是,如果您使用模板,则意味着您想要编译时多态性,而不是运行时多态性。
这是奖章的一面。另一方面,编写不当的模板代码会导致不必要的代码膨胀,因为在 运行 时间多态性很容易实现相同目标的情况下,会强制执行编译时多态性(即相同代码的重复实例化)更少的代码膨胀和可忽略的性能损失。从这个角度来看,隐式自动切换到模板代码中的 运行 时间多态性可能是有益的。但我相信当前状态下的语言并不是为此量身定做的。这是您应该明确自己做的事情。