F
@Quiche-Lorraine Nach meinem Verständnis ist es noch immer ein Singleton, wenn es nur eine Instanz gibt. Das Pattern mit Project::GetInst() stellt halt lediglich sicher, dass es auch tatsächlich nur eine Instanz geben kann, besonders wenn man den Konstruktor von Project auch noch private macht. Project::GetInst() erlaubt es auch das Projekt zu referenzieren, bevor main() aufgerufen wurde, z.B. während der Intitialisierung einer anderen statischen Variablen, vielleicht auch in einer anderen Übersetzungseinheit.
shared_ptr sehe ich eher als Werkzeug wenn es keine eindeutige Stelle im Programmcode gibt, wo man die Project-Instanz nicht mehr benötigt und sie so lange am Leben erhalten muss, bis alle damit fertig sind. Z.B. wenn einige statische Objekte noch aktiv sein können, nachdem main() beendet wurde oder wenn man mit mehreren Threads zu tun hat, wo nicht ganz klar ist welcher davon vor dem anderen fertig ist und sich das auch mit jeder Ausführung des Programms ändert. GUI-Objekte wie hier vermutlich (?) sehe ich auch als guten Anwendungsfall für shared_ptr.
Dein Beispielcode jedenfalls könnte auch eine simple automatische Variable verwenden, die hätte weniger Overhead beim Zugriff:
int main()
{
Project Proj;
OptionsDialog D(Proj);
} // <- Project-Instanz wird hier zerstört, genau wie auch beim std::shared_pr<Project>
Der Ansatz mit dem shared_ptr hat aber den Vorteil, dass jeder OptionsDialog jetzt theoretisch eine andere Instanz von Project referenzieren kann und diese auch so lange am Leben bleibt, wie ein OptionsDialog (oder ein anderes Objekt) sie benötigt. Du kannst also mehrere Projekte "offen" haben.
Wenn du tatsächlich nur eine einzige Project-Instanz benötigst, dann finde ich den Project::GetInst()-Ansatz durchaus okay so wie er ist. Wenn du aber mehr Flexibilität willst, z.B. weil du irgendwann mal mehrere Instanzen haben oder du den Code etwas mehr von Project entkoppeln willst, dann kann man da eventuell so erreichen:
auto get_project() -> Project&
{
return Project::GetInst();
}
class OptionsDialog {
public:
void some_function()
{
auto& project = get_project();
project.dies();
project.das();
}
};
Das sieht erstmal banal aus, aber du bist damit nicht mehr darauf festgenagelt, wie auf die eigentliche Project-Instanz zugegriffen wird. Z.B. könntest du irgendwann das aktive Projekt während das Programm läuft ändern wollen (immer noch nur ein globales Projekt, aber du kannst die Instanz austauschen):
g_project = std::make_unique<Project>();
void load_project(std::string_view project_file)
{
g_project = std::make_unique<Project>(project_file);
}
auto get_project() -> Project&
{
return *g_project;
}
class OptionsDialog {
public:
void some_function()
{
auto& project = get_project();
project.dies();
project.das();
}
};
Oder du entscheidest dich irgendwann mal für den shared_ptr-Ansatz mit beliebig vielen "offenen" Projekten, dann kannst du get_project einfach als Member-Funktion implementieren:
class OptionsDialog {
public:
...
auto get_project() -> Project&
{
return *mProject;
}
void some_function()
{
auto& project = get_project();
project.dies();
project.das();
}
private:
std::shared_ptr<Project> mProject;
};
Der Gag an dem Ansatz ist, dass der Code, der auf die Projekt-Instanz zugreift identisch bleibt und du ihn nicht mehr anpassen musst, egal ob du ein Singleton, eine globale austauschbare Variable, einen shared_ptr Member oder was auch immer verwendest.
Kurz: Die Entkopplung von Project erreichst du nicht durch den shared_ptr (auch wenn der durchaus eine gute Idee sein kann) sondern indem du das Interface vereinheitlichst, mit dem auf das Projekt zugegriffen wird.