在设计 DSL(编译成 C++)时,我发现定义一个包装类很方便,在销毁时,它会调用 .free()
包含类的方法:
template<class T>
class freeOnDestroy : public T {
using T::T;
public:
operator T&() const { return *this; }
~freeOnDestroy() { T::free(); }
};
包装器设计为完全透明:所有方法、重载和构造函数都继承自 T
(至少据我所知),但是当包含在包装器中时, free() 方法在销毁时被调用。请注意,我明确避免使用 T
自 T::free()
以来的析构函数和 ~T()
可能有不同的语义!
所有这些工作正常,直到包装类被用作非引用模板化调用的成员,此时 freeOnDestroy
被实例化,在包装对象上调用 free。我希望发生的是使用 T
的模板方法。而不是 freeOnDestroy<T>
,并将参数隐式转换为父类(super class)。下面的代码示例说明了这个问题:
// First class that has a free (and will be used in foo)
class C{
int * arr;
public:
C(int size){
arr = new int[size];
for (int i = 0; i < size; i++) arr[i] = i;
}
int operator[] (int idx) { return arr[idx]; }
void free(){ cout << "free called!\n"; delete []arr; }
};
// Second class that has a free (and is also used in foo)
class V{
int cval;
public:
V(int cval) : cval(cval) {}
int operator[] (int idx) { return cval; }
void free(){}
};
// Foo: in this case, accepts anything with operator[int]
// Foo cannot be assumed to be written as T &in!
// Foo in actuality may have many differently-templated parameters, not just one
template<typename T>
void foo(T in){
for(int i = 0; i < 5; i++) cout << in[i] << ' ';
cout << '\n';
}
int main(void){
C c(15);
V v(1);
freeOnDestroy<C> f_c(15);
foo(c); // OK!
foo(v); // OK!
foo<C>(f_c); // OK, but the base (C) of f_c may not be explicitly known at the call site, for example, if f_c is itself received as a template
foo(f_c); // BAD: Creates a new freeOnDestroy<C> by implicit copy constructor, and uppon completion calls C::free, deleting arr! Would prefer it call foo<C>
foo(f_c); // OH NO! Tries to print arr, but it has been deleted by previous call! Segmentation fault :(
return 0;
}
我应该提到的一些非解决方案是:
- 制作
freeOnDestroy::freeOnDestroy(const freeOnDestroy &src)
显式和私有(private),但这似乎覆盖了T
的构造函数。我希望它会尝试将其隐式转换为T
并将其用作模板参数。 - 假设
foo
接收其模板化参数的引用(如void foo(T &in)
:这既不是这种情况,在某些情况下也不理想 - 始终显式模板调用
foo
, 如foo<C>(f_c)
:f_c
本身可能是模板化的,所以很难知道实例化foo
与C
(是的,这可以通过创建多个版本的foo
来完成,一个一个地删除包装器,但是如果不为foo
的每个模板化参数创建不同的重载,我找不到这样做的方法)。
总而言之,我的问题是:是否有一种干净的方法来确保在解析模板时将基类转换为其父类(super class)?或者,如果没有,是否有某种使用 SFINAE 的方法,当模板参数是包装类的实例时导致替换失败,从而强制它使用隐式转换到包装类(不复制每个 foo
-类似方法签名可能有几十次)?
我目前有一个涉及 DSL 更改的变通办法,但我对此并不完全满意,并且很好奇是否完全有可能设计一个按描述工作的包装器类。
最佳答案
这里的问题不是当“包装类被用作非引用模板调用的成员”时。
这里的问题是模板包装器——可能还有它的父类(super class)——有violated the Rule Of Three .
将类的实例作为非引用参数传递只是“按值传递”的另一种说法。按值传递会复制类的实例。您的模板类——最有可能的是它的包装类——都没有显式复制构造函数;因此,类的复制实例并不知道它是一个拷贝,因此析构函数执行它认为应该执行的操作。
这里正确的解决方案不是破解某些东西来传递 freeOnDestroy<T>
的实例。按值(value)结束复制 T
, 而不是 freeOnDestroy<T>
.正确的解决方案是为 freeOnDestroy
添加适当的复制构造函数和赋值运算符。模板,以及可能使用它的任何父类(super class),以便一切都符合三规则。
关于c++ - 在模板调用中将包装器类隐式转换为父类(super class),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38165372/