2016-12-08 2 views
6

In einer ziemlich großen Anwendung möchte ich einige Statistiken über Objekte einer bestimmten Klasse verfolgen. Um die Leistung nicht zu beeinträchtigen, möchte ich, dass die Statistiken in einer Pull-Konfiguration aktualisiert werden. Daher muss ich an jedem Ort einen Verweis auf jedes Live-Objekt haben. Gibt es eine idiomatische Weg:Verfolgen von (stack-allocated) Objekten

  1. erstellen, suchen, iterieren solche Referenzen
  2. verwalten sie automatisch (also entfernen Sie den Verweis auf die Zerstörung)

I in Form eines Satzes denke von Smart Pointern hier, aber die Speicherverwaltung wäre etwas invertiert: Anstatt das Objekt zu zerstören, wenn der Smart Pointer zerstört wird, möchte ich, dass der Smart Pointer entfernt wird, wenn das Objekt zerstört wird. Im Idealfall möchte ich das Rad nicht neu erfinden.

Ich könnte mit einer Verzögerung bei der Entfernung der Zeiger leben, ich würde nur einen Weg brauchen, um sie schnell ungültig zu machen.

bearbeiten: Weil Paddy darum gebeten hat: Der Grund für die Pull-basierte Sammlung ist, dass das Erlangen der Information relativ teuer sein kann. Pushing ist offensichtlich eine saubere Lösung, wird aber als zu teuer angesehen.

+0

Welche Art von Statistiken benötigen Sie? Wenn Sie nur die Anzahl der Live-Instanzen verfolgen, dann überlegen Sie, ob Sie eine einzelne atomare Ganzzahl pro Klasse speichern und vielleicht einen einfachen RAII-Wrapper zum Zählen erstellen (das heißt, Sie geben ihn einfach in Ihre Klassendefinition ein und die Arbeit ist erledigt). – paddy

+2

Wenn es etwas nützt, [hier ist ein einfaches Beispiel] (http://coliru.stacked-crooked.com/a/5a25d7a6a2ec18a0) von dem, worüber ich gesprochen habe. Sie können Ihre Objekte nicht iterieren oder durchsuchen, aber es ist nicht klar, warum Sie das tun möchten. Es ist wahrscheinlich keine gute Idee, auf einen anderen Stack zuzugreifen. In diesem Beispiel werden Objektanzahlwerte mit sehr geringem Overhead und Thread-sicher bereitgestellt. – paddy

+0

Das wäre im Grunde ein Push-Update, wird aber als zu teuer angesehen. – choeger

Antwort

2

Es gibt keine spezielle Eigenschaft der Sprache, die Ihnen dies ermöglicht. Manchmal wird die Objektverfolgung durch das Rollen des eigenen Speicherzuordners gehandhabt, aber dies funktioniert nicht einfach auf dem Stapel.

Wenn Sie jedoch nur den Stapel verwenden, wird Ihr Problem dadurch leichter, vorausgesetzt, dass die zu verfolgenden Objekte auf einem einzelnen Thread liegen. C++ gibt besondere Garantien über die Reihenfolge der Konstruktion und Zerstörung auf dem Stapel. Das heißt, die Zerstörungsreihenfolge ist genau umgekehrt zur Konstruktionsreihenfolge.

Und so können Sie dies verwenden, um einen einzelnen Zeiger in jedem Objekt zu speichern, plus einen statischen Zeiger, um den neuesten zu verfolgen. Jetzt haben Sie einen Objektstapel als verkettete Liste dargestellt.

template <typename T> 
class Trackable 
{ 
public: 
    Trackable() 
    : previous(current()) 
    { 
     current() = this; 
    } 

    ~Trackable() 
    { 
     current() = previous; 
    } 

    // External interface 
    static const T *head() const { return dynamic_cast<const T*>(current()); } 
    const T *next() const { return dynamic_cast<const T*>(previous); } 

private: 
    static Trackable * & current() 
    { 
     static Trackable *ptr = nullptr; 
     return ptr; 
    } 

    Trackable *previous; 
} 

Beispiel:

struct Foo : Trackable<Foo> {}; 
struct Bar : Trackable<Bar> {}; 

// ::: 

// Walk linked list of Foo objects currently on stack. 
for(Foo *foo = Foo::head(); foo; foo = foo->next()) 
{ 
    // Do kung foo 
} 

Nun freilich ist dies eine sehr einfache Lösung. In einer großen Anwendung können Sie mehrere Stapel verwenden, die Ihre Objekte verwenden. Sie könnten Stacks in mehreren Threads verarbeiten, indem Sie current() die Semantik thread_local verwenden. Obwohl Sie etwas Magie benötigen, um das zu funktionieren, müsste head() auf eine Registrierung von Threads zeigen, und das würde Synchronisierung erfordern.

Sie auf jeden Fall do nicht alle Stapel in einer einzigen Liste synchronisiert werden sollen, denn das Ihr Programm Leistungsskalierbarkeit töten.

Für Ihre Pull-Anforderung nehme ich an, dass es ein separater Thread ist, der über die Liste gehen möchte. Sie würden eine Möglichkeit zur Synchronisierung benötigen, so dass alle neuen Objektkonstruktionen oder -zerstörungen innerhalb von Trackable<T> blockiert werden, während die Liste iteriert wird. O.ä.

Aber zumindest könnte man diese Grundidee nehmen und auf Ihre Bedürfnisse erweitern.

Denken Sie daran, dass Sie diese einfache Liste nicht verwenden können, wenn Sie Ihre Objekte dynamisch zuweisen. Dafür benötigen Sie eine bidirektionale Liste.

2

Der einfachste Ansatz besteht darin, Code innerhalb jedes Objekts zu haben, damit er sich bei der Instanziierung registriert und sich bei der Zerstörung selbst entfernt.

template <class T> 
struct AutoRef { 

    static auto &all() { 
     static std::set<T*> theSet; 
     return theSet; 
    } 

private: 
    friend T; 

    AutoRef() { all().insert(static_cast<T*>(this)); } 
    ~AutoRef() { all().erase(static_cast<T*>(this)); } 
}; 

Jetzt kann eine Foo Klasse erbt von AutoRef<Foo> hat ihre Instanzen verwiesen innerhalb AutoRef<Foo>::all(): Dieser Code kann leicht mit einem CRTP injiziert werden.

See it live on Coliru

+0

Viel einfacher als meine Lösung =) Obwohl dies logarithmische Komplexität hat. Ich nahm sie ernst, als sie sich beschwerten, dass ein Atominkrement und -dekrement pro Objekt "zu teuer" sei. – paddy

+0

Ein Problem unserer beiden Lösungen besteht auch darin, dass Sie nicht garantieren können, dass ein Objekt, das sich noch in der Registrierung befindet, gerade nicht zerstört wird und daher ungültig ist, selbst wenn Sie den Zugriff über Threads synchronisieren. Es ist nicht schwer, dieses Verhalten zu ändern, aber es ist ein besonderes Detail, das man sich überlegen sollte. – paddy

+0

@paddy OP möchte nicht eifrig Statistiken pushen, weil sie in den meisten Fällen langsam zu generieren und zu ignorieren sind. Keine Erwähnung von Zählung oder Multithreading. Der Container kann je nach Nutzungsmuster (Einfüge-/Entfernungsrate, Anzahl der Abfragen usw.) einfach gegen einen anderen ausgetauscht werden. – Quentin

Verwandte Themen