In einen DSL-Gestaltung (die in C kompiliert ++), fand ich es bequem, eine Wrapper-Klasse, die, uppon Zerstörung zu definieren, eine .free()
Methode auf dem enthaltenen Klasse aufrufen würde:Implizit Guss Hüllklasse in templated Aufruf supperclass
Der Wrapper ist vollständig transparent: Alle Methoden, Überladungen und Konstruktoren sind geerbt von T
(zumindest meines Wissens), aber wenn sie in den Wrapper enthalten ist, wird die free() -Methode aufgerufen Zerstörung. Beachten Sie, dass ich die Verwendung des Destruktors T
explizit vermeiden möchte, da T::free()
und ~T()
unterschiedliche Semantik haben können!
All dies funktioniert einwandfrei, bis eine umbrochene Klasse als Mitglied für einen nicht referenzierten Aufruf verwendet wird. An diesem Punkt wird freeOnDestroy
instanziiert, wobei das umschlossene Objekt frei aufgerufen wird. Was ich würde wie passieren, ist für die templated Methode, T
anstelle von freeOnDestroy<T>
zu verwenden, und den Parameter implizit in die Supperclass zu werfen. Das folgende Codebeispiel veranschaulicht dieses Problem:
// 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;
}
Einige nicht-Lösungen sollte ich erwähnen sind:
- machen
freeOnDestroy::freeOnDestroy(const freeOnDestroy &src)
expliziten und privat, aber dies scheintT
‚s Konstruktor außer Kraft zu setzen. Ich hatte gehofft, es würde versuchen, es implizit inT
konvertieren und das als Vorlage Argument verwenden. - Angenommen
foo
erhält einen Verweis seiner Templat-Argumente (wie invoid foo(T &in)
: Dies ist weder der Fall ist, noch in einigen Fällen wünschenswert - immer den Aufruf
foo
explizit Vorlage, wie infoo<C>(f_c)
:f_c
selbst als Templat werden können, so Es ist schwer zu wissen,foo
mitC
instanziieren (ja, dies könnte mit mehreren Versionen vonfoo
getan werden, um die Wrapper eins nach dem anderen zu entfernen, aber ich kann keinen Weg finden, dies zu tun, ohne eine andere Überladung für jede Vorlage erstellen Argumentfoo
)
Zusammengefasst ist meine Frage: Gibt es eine saubere (ish) -Methode, um sicherzustellen, dass eine Basisklasse in ihre Superklasse gegossen wird, wenn eine Vorlage aufgelöst wird? Oder, wenn nicht, gibt es eine Möglichkeit, SFINAE zu verwenden, indem ein Substitutionsfehler verursacht wird, wenn das Template-Argument eine Instanz der Wrapper-Klasse ist, und somit gezwungen wird, die implizite Umwandlung in die Wrapped-Klasse zu verwenden (ohne jede foo
-like Methodensignatur möglicherweise Dutzende Male)?
Ich habe derzeit eine Arbeit-Umgebung, die Änderungen in der DSL beinhaltet, aber ich bin nicht ganz glücklich damit, und war neugierig, ob es überhaupt möglich war, eine Wrapper-Klasse zu entwerfen, die wie beschrieben funktioniert.
Hatte nicht an diese Alternative gedacht, und ja, das Hinzufügen einer 'bool isCopy' und das Initialisieren/Überprüfen es passend zu' freeOnDestroy' löst das Problem, obwohl auf Kosten eines extra boolean und eine Verzweigung für jedes Mal die Die aufgerufene Funktion wird zurückgegeben - wahrscheinlich nicht besonders teuer (obwohl ich ein wenig besorgt bin, könnte die zusätzliche Größe Block-Missausrichtung verursachen und den C++ - Compiler-Optimierer verwirren, der dann SEHR teuer sein könnte). Was gutes Design angeht, ist dies eine korrekte Lösung. –