1

tl; dr: Gibt es eine Möglichkeit, ein Standardargument aus dem aktuellen Bereich zu allen impliziten Konstruktoren in C++ hinzuzufügen?Implizites Argument für Konvertierungskonstruktoren

Ich entwickle derzeit eine Schnittstelle für eine eingebettete Sprache in C++. Ziel ist es, syntaktisch korrekte Ausdrücke typsicher und komfortabel zu erstellen. Gerade jetzt, denke ich, dass das Erlernen einer schwergewichtigen Implementierung wie boost :: proto eine zu große Latenz in die Entwicklung bringen wird, also versuche ich meine eigene Implementierung zu rollen.

Hier ist eine kleine Demo:

#include <iostream> 
#include <string> 
#include <sstream> 

class ExprBuilder 
{ 
public: 
    ExprBuilder(const int val) : val(std::to_string(val)) {} 
    ExprBuilder(const std::string val) : val(val) {} 
    ExprBuilder(const char* val) : val(val) {} 

    ExprBuilder(const ExprBuilder& lhs, const ExprBuilder& arg) { 
     std::stringstream ss; 
     ss << "(" << lhs.val << " " << arg.val << ")"; 
     val = ss.str(); 
    } 

    const ExprBuilder operator()(const ExprBuilder& l) const { 
     return ExprBuilder(*this, l); 
    } 

    template<typename... Args> 
    const ExprBuilder operator()(const ExprBuilder& arg, Args... args) const 
     { 
      return (*this)(arg)(args...) ; 
     } 

    std::string val; 
}; 

std::ostream& operator<<(std::ostream& os, const ExprBuilder& e) 
{ 
    os << e.val; 
    return os; 
} 

int main() { 
    ExprBuilder f("f"); 
    std::cout << f(23, "foo", "baz") << std::endl; 
} 

Wie Sie sehen können, ist es ziemlich einfach Ausdrücke embedd aufgrund von C++ Überlastung und implizite Konvertierungen.

Ich stehe jedoch vor einem praktischen Problem: Im obigen Beispiel wurden alle Daten in Form von std :: string-Objekten zugewiesen. In der Praxis brauche ich etwas Komplexeres (AST-Knoten), die auf dem Heap zugewiesen und von einem dedizierten Besitzer verwaltet werden (Legacy-Code, kann nicht geändert werden). Also muss ich ein eindeutiges Argument übergeben (besagter Eigentümer) und es für die Zuweisungen verwenden. Ich würde hier lieber kein statisches Feld verwenden.

Was ich suche ist eine Möglichkeit zu verwenden fragen Sie den Benutzer, einen solchen Besitzer jedes Mal, wenn der Builder verwendet wird, aber auf eine bequeme Weise zu stellen. So etwas wie eine dynamisch begrenzte Variable wäre großartig. Gibt es eine Möglichkeit, in C++ folgendes zu erhalten:

class ExprBuilder 
{ 
    ... 
    ExprBuilder(const ExprBuilder& lhs, const ExprBuilder& arg) { 
     return ExprBuilder(owner.allocate(lhs, rhs)); // use the owner implicitly 
    } 
    ... 
}; 

int main() { 
    Owner owner; // used in all ExprBuilder instances in the current scope 
    ExprBuilder f("f"); 
    std::cout << f(23, "foo", "baz") << std::endl; 
} 

Ist das möglich?

bearbeiten: Ich möchte klarstellen, warum ich (bis jetzt) ​​nicht eine globale Variable betrachte. Der Besitzer muss irgendwann vom Benutzer des Builders manuell freigegeben werden. Daher kann ich keine Ad-hoc-Datei erstellen. Daher kann der Benutzer den Besitzer insgesamt "vergessen". Um dies zu vermeiden, suche ich einen Weg, die Anwesenheit des Besitzers durch den Typchecker zu erzwingen.

+0

denke ich, was Sie sagen, eine Art 'global variable' ist –

+0

Sie eine statische/globale Variable mit' Owner' Raii gesetzt, und verwenden, die globale Variable in 'ExprBuilder'. – Jarod42

+0

Sie meinen ein unique_ptr als globale Variable, um sicherzustellen, dass es nur im Kontext verwendet wird und explizit beibehalten werden muss? Würde dies nicht zu Laufzeitfehlern führen, wenn der Zeiger vom Benutzer vergessen wird? – choeger

Antwort

1

Dies ist ohne globale/statische Variablen kaum möglich, da ohne globale/statische Informationen die lokalen Variablen Owner owner und ExprBuilder f nichts voneinander wissen können.

Ich denke, der sauberste Weg, um ein

static Owner* current_owner; 

zur ExprBuilder Klasse hinzuzufügen ist. Dann können Sie eine neue Klasse ScopedCurrentOwnerLock hinzufügen, die current_owner im Konstruktor setzt und im Destruktor auf nullptr setzt. Dann können Sie es ähnlich Sperre zu einem Mutex verwenden:

class ScopedCurrentOwnerLock { 
public: 
    ScopedCurrentOwnerLock(Owner const& owner) { 
     ExprBuilder::current_owner = &owner; 
    } 
    ~ScopedCurrentOwnerLock() { 
     ExprBuilder::current_owner = nullptr; 
    } 
}; 

int main() { 
    Owner owner; 
    ScopedCurrentOwnerLock lock(owner); 
    ExprBuilder f("f"); 
} 

Wenn Sie Zugang zum Owner Code haben, können Sie die ScopedCurrentOwnerLock Klasse weglassen und direkt aktiviert/deaktiviert den Zeiger im Konstruktor/Destruktor Owner.

Bitte beachten Sie die folgenden zwei Probleme mit dieser Lösung:

  • Wenn der Eigentümer den Gültigkeitsbereich verlässt, bevor die Sperre den Gültigkeitsbereich verlässt, Sie haben einen ungültigen Zeiger.

  • Der statische Zeiger hat ein unvorhersagbares Verhalten, wenn Sie mehrere Sperren gleichzeitig haben, z. G. aufgrund von Multithreading.

0

Alle Ihre ExprBuilders haben eine Abhängung auf Owner, und Sie globalen Zustand zu Recht nicht wollen. Sie müssen also den Besitzer an jeden Konstruktor übergeben.

Wenn Sie nicht wirklich owner, zu all Ihren Instanziierungen in einem Block hinzufügen möchten, können Sie eine Factory erstellen, um sie für Sie zu übergeben.

struct ExprBuilderFactory 
{ 
    Owner & owner; 
    ExprBuilder operator()(int val) { return ExprBuilder(owner, val); } 
    ExprBuilder operator()(char * val) { return ExprBuilder(owner, val); } 
    // etc 
} 

int main() { 
    Owner owner; 
    ExprBuilderFactory factory{ owner }; 
    ExprBuilder f = factory("f"); 
} 
Verwandte Themen