При разработке 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 для обернутого объекта. Я хотел бы, чтобы метод tempated использовал T
вместо freeOnDestroy<T>
и неявно приводил параметр к суперклассу. Следующий пример кода иллюстрирует эту проблему:
// 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)
явным и закрытым, но это, кажется, переопределяет конструкторT
. Я надеялся, что он попытается неявно преобразовать его вT
и использовать его в качестве аргумента шаблона. - Предположим, что
foo
получает ссылку на свои шаблонные аргументы (как вvoid foo(T &in)
: в некоторых случаях это не так и нежелательно). - Всегда явно шаблонизируйте вызов
foo
, как вfoo<C>(f_c)
: самf_c
может быть шаблонным, поэтому трудно понять, как создать экземплярfoo
сC
(да, это можно сделать, создав несколько версийfoo
, чтобы удалить оболочки одну за другой, но я не могу найти способ сделать это без создания другой перегрузки для каждого шаблонного аргументаfoo
).
Таким образом, мой вопрос таков: существует ли чистый метод, обеспечивающий приведение базового класса к его суперклассу при разрешении шаблона? Или, если нет, есть ли способ использовать SFINAE, вызывая ошибку подстановки, когда аргумент шаблона является экземпляром класса-оболочки, и, таким образом, заставляя его использовать неявное приведение к обернутому классу (без дублирования каждого foo
-подобного сигнатура метода, возможно, десятки раз)?
В настоящее время у меня есть обходной путь, который включает изменения в DSL, но я не совсем доволен им, и мне было любопытно, возможно ли вообще разработать класс-оболочку, который работает, как описано.