Frage

In einem C ++ - Programm benötige ich einige Helfer -konstante Objekte, die einmal so instanziiert würden, vorzugsweise zu Beginn des Programms. Diese Objekte würden hauptsächlich in derselben Übersetzungseinheit verwendet. Der einfachste Weg, dies zu tun, wäre, sie statisch zu machen:

static const Helper h(params);

Aber dann ist da noch das Statische Initialisierungsreihenfolge Problem, also wenn Helper bezieht sich auf einige andere Statik (über params), Dies könnte zu UB führen.

Ein weiterer Punkt ist, dass ich dieses Objekt möglicherweise zwischen mehreren Einheiten teilen muss. Wenn ich es einfach lasse static und geben Sie eine .H -Datei ein, die zu mehreren Objekten führen würde. Ich könnte das vermeiden, indem ich mich darum kümmerte extern usw., aber dies kann endlich die gleichen Initialisierungsauftragsprobleme provozieren (und nicht zu sagen, dass es sehr c-ish aussieht).

Ich dachte an Singletons, aber das wäre übertrieben aufgrund des Boilerplate -Code und der unbequemen Syntax (z. B. MySingleton::GetInstance().MyVar) - Diese Objekte sind Helfer, also sollen sie Dinge vereinfachen, um sie nicht zu komplizieren ...

Das gleiche C ++ - FAQ Erwähnungen diese Option:

 Fred& x()
 {
   static Fred* ans = new Fred();
   return *ans;
 } 

Wird das wirklich benutzt und als eine gute Sache angesehen? Soll ich es so machen oder würden Sie andere Alternativen vorschlagen? Vielen Dank.

Bearbeiten: Ich hätte klarstellen sollen, warum ich tatsächlich Helfer benötige: Sie sind sehr wie normale Konstanten und hätten vorbereitet worden sein, aber es ist bequemer, dies zur Laufzeit zu tun. Ich würde es bevorzugen, sie vor Main zu instanziieren, da es automatisch Multi-Threading-Probleme auflöst (gegen die lokale Statik in C ++ 03 nicht geschützt ist). Wie ich bereits sagte, sind sie oft auf eine Übersetzungseinheit beschränkt, sodass es nicht sinnvoll ist, sie zu exportieren und in Main () zu initialisieren. Sie können sich sie nur als Konstanten vorstellen, aber nur zur Laufzeit bekannt.

War es hilfreich?

Lösung

Es gibt mehrere Möglichkeiten für den globalen Staat (ob veränderlich oder nicht).

Wenn Sie befürchten, dass Sie ein Initialisierungsproblem haben, sollten Sie die verwenden local static Ansatz, um Ihre Instanz zu erstellen.

Beachten Sie, dass das klobige Singleton -Design, das Sie präsentieren, kein obligatorisches Design ist:

class Singleton
{
public:
  static void DoSomething(int i)
  {
    Singleton& s = Instance();
    // do something with i
  }


private:
  Singleton() {}
  ~Singleton() {}

  static Singleton& Instance()
  {
    static Singleton S; // no dynamic allocation, it's unnecessary
    return S;
  }
};

// Invocation
Singleton::DoSomething(i);

Ein weiteres Design ist etwas ähnlich, obwohl ich es sehr bevorzuge, da es viel einfacher wird.

class Monoid
{
public:
  Monoid()
  {
    static State S;
    state = &s;
  }

  void doSomething(int i)
  {
    state->count += i;
  }

private:
  struct State
  {
    int count;
  };

  State* state;
};


// Use
Monoid m;
m.doSomething(1);

Der Nettovorteil hier ist, dass die "globale Ness" des Staates versteckt ist. Es handelt sich um eine Implementierungsdetails, über die Kunden keine Sorgen machen müssen. Sehr nützlich für Caches.

Lassen Sie uns das Design in Frage stellen:

  • Müssen Sie tatsächlich die Singularität durchsetzen?
  • Müssen Sie das Objekt tatsächlich zuvor erstellt werden? main Startet?

Singularität wird im Allgemeinen überbetont. C ++ 0x hilft hier, aber selbst dann kann es sehr nervig sein, technisch Singularität zu ersetzen, anstatt sich auf Programmierer zu verlassen, um sich zu verhalten ... zum Beispiel beim Schreiben von Tests: Möchten Sie Ihr Programm wirklich entladen/neu laden? Nur um die Konfiguration zwischen den einzelnen zu ändern? Pfui. Es ist viel einfacher, es einmal zu instanziieren und Vertrauen in Ihre Mitprogrammierer zu haben ... oder an Funktionstests;)

Die zweite Frage ist technischer als funktional. Wenn Sie die Konfiguration vor dem Einstiegspunkt Ihres Programms benötigen, können Sie sie einfach lesen, wenn sie startet.

Es mag naiv klingen, aber es gibt tatsächlich ein Problem mit dem Computer während der Bibliothekslast: Wie gehen Sie mit Fehlern um? Wenn Sie werfen, ist die Bibliothek nicht geladen. Wenn Sie nicht werfen und weitermachen, befinden Sie sich in einem ungültigen Zustand. Nicht so lustig, oder? Die Dinge sind viel einfacher, sobald die eigentliche Arbeit begonnen hat, da Sie die reguläre Kontroll-Flow-Logik verwenden können.

Und wenn Sie darüber nachdenken, zu testen, ob der Staat gültig ist oder nicht ... Warum nicht alles an dem Punkt aufbauen, an dem Sie testen würden?

Schließlich das Problem mit global ist die versteckten Abhängigkeiten, die eingeführt werden. Es ist viel besser, wenn Abhängigkeiten implizit die Grundlage für den Ausführungfluss oder die Auswirkungen eines Refactorings haben.


BEARBEITEN:

In Bezug auf Initialisierungsauftragsprobleme: Objekte innerhalb einer einzelnen Übersetzungseinheit werden in der von ihnen definierten Reihenfolge garantiert initialisiert.

Daher ist der folgende Code gemäß dem Standard gültig:

static int foo() { return std::numeric_limits<int>::max() / 2; }
static int bar(int c) { return c*2; }

static int const x = foo();
static int const y = bar(x);

Die Initialisierungsreihenfolge ist nur ein Problem, wenn Konstanten / Variablen in einer anderen Übersetzungseinheit definiert werden. Als solche, static Objekte können natürlich ohne Probleme ausgedrückt werden, solange sie sich nur beziehen static Objekte in derselben Übersetzungseinheit.

In Bezug auf das Raumproblem: die as-if Regel kann hier Wunder bewirken. Informell die as-if Regel bedeutet, dass Sie ein Verhalten angeben und es dem Compiler/Linker/der Laufzeit überlassen, um es bereitzustellen, ohne dass die Welt vorhanden ist. Dies ermöglicht tatsächlich Optimierungen.

Wenn die Compiler -Kette dadurch schließen kann, dass die Adresse einer Konstante niemals genommen wird, kann sie die Konstante insgesamt elide können. Wenn es schließen kann, dass mehrere Konstanten immer gleich sind und dass ihre Adresse nie inspiziert wird, kann sie sie zusammenführen.

Andere Tipps

Ja, Sie können verwenden Konstrukt auf den ersten Gebrauch konstruieren Idiom, wenn es Ihr Problem vereinfacht. Es ist immer besser als globale Objekte, deren Initialisierung abhängen auf anderen globalen Objekten.

Die andere Alternative ist Singleton -Muster. Beide können ein ähnliches Problem lösen. Aber Sie müssen entscheiden, welche Situation besser passt und Ihre Anforderungen erfüllen.

Nach meinem besten Wissen gibt es nichts "Besseres" als diese beiden Appproaches.

Singletons und globale Objekte werden oft als böse angesehen. Der einfachste und flexibelste Weg ist, das Objekt in Ihrem zu instanziieren main Funktion und übergeben dieses Objekt an andere Funktionen:

void doSomething(const Helper& h);
int main() {
  const Parameters params(...);
  const Helper h(params);
  doSomething(h);
}

Eine andere Möglichkeit besteht darin, die Helferfunktionen nicht zu machen. Vielleicht brauchen sie überhaupt keinen Zustand, und wenn dies der Fall ist, können Sie ein staatliches Objekt bestehen, wenn Sie sie anrufen.

Ich denke, nichts spricht gegen das in den FAQ erwähnte lokale statische Idiom. Es ist einfach und sollte fadensicher sein, und wenn das Objekt nicht veränderlich ist, sollte es auch leicht verspottbar sein und keine Aktion in der Ferne einführen.

Tut Helper müssen vorher existieren main läuft? Wenn nicht, machen Sie einen (Satz?) Global Zeigervariablen initialisiert zu 0. Verwenden Sie dann Main, um sie in einer endgültigen Reihenfolge mit dem konstanten Zustand zu bevölkern. Wenn Sie mögen, können Sie sogar Helferfunktionen erstellen, die die Derreferenz für Sie erfüllen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top