Frage

An meinem Arbeitsplatz, neigen wir dazu, zu verwenden Iostream , string , Vektor , Karte , und die ungeraden Algorithmus oder zwei. Wir haben nicht wirklich viele Situationen gefunden, wo Vorlage Techniken waren eine beste Lösung für ein Problem.

Was ich suche hier gibt es Ideen und optional Beispielcode, der zeigt, wie Sie eine Vorlage Technik verwendet, um eine neue Lösung für ein Problem zu schaffen, die man im wirklichen Leben begegnet.

Als Bestechung, erwartet eine abstimmen für Ihre Antwort.

War es hilfreich?

Lösung

Ich habe eine Menge von Template-Code verwendet, meist in Boost und der STL, aber ich hatte selten eine Notwendigkeit, write vorhanden.

Eine der Ausnahmen, vor ein paar Jahren, war in einem Programm, das Windows PE-Format EXE-Dateien manipuliert. Das Unternehmen wollte 64-Bit-Unterstützung hinzuzufügen, aber die ExeFile Klasse, die ich die Dateien zu handhaben geschrieben hatte funktionierte nur mit 32-Bit-Einsen. Der Code benötigt, um die 64-Bit-Version zu manipulieren, war im wesentlichen identisch, aber es benötigt einen anderen Adresstyp (64-Bit anstelle von 32-Bit) zu verwenden, die zwei andere Datenstrukturen verursacht auch anders sein.

Basierend auf der Verwendung des STL einer einzigen Vorlage sowohl std::string und std::wstring zu unterstützen, habe ich beschlossen, eine Vorlage zu versuchen ExeFile machen, mit den unterschiedlichen Datenstrukturen und dem Adresstyp als Parameter. Es gab zwei Orte, an denen ich zu tun hatte noch zu #ifdef WIN64 Linien (leicht unterschiedliche Verarbeitungsanforderungen), aber es war nicht wirklich schwierig zu verwenden. Wir haben volle 32- und 64-Bit-Unterstützung in diesem Programm jetzt, und die Vorlage bedeuten, mit, dass jede Modifikation wir da automatisch auf beiden Versionen gilt getan haben.

Andere Tipps

Allgemeine Informationen über Vorlagen:

Vorlagen sind nützlich, zu jeder Zeit müssen Sie den gleichen Code verwenden, aber auf verschiedene Datentypen arbeiten, wo die Typen zum Zeitpunkt der Kompilierung bekannt sind. Und auch wenn Sie jede Art von Container-Objekt.

Eine sehr häufige Nutzung ist für fast jede Art von Datenstruktur. Zum Beispiel: Einfach verkettete Listen, doppelt verkettete Listen, Bäume, versucht, Hash-Tabellen, ...

Eine weitere sehr häufige Verwendung ist Algorithmen zum Sortieren.

Einer der wichtigsten Vorteile von Vorlagen ist, dass Sie Code-Duplizierung entfernen können. Code-Duplizierung ist eines der größten Dinge, die Sie vermeiden sollten bei der Programmierung.

Sie können eine Funktion Max, da beide ein Makro oder eine Vorlage implementieren, aber die Vorlage Implementierung würde Art sicher und daher besser sein.

Und jetzt auf die coolen Sachen:

Siehe auch Metaprogrammierung , die zum Zeitpunkt der Kompilierung eine Art und Weise des Pre-Auswertung Code ist anstatt zur Laufzeit. Metaprogrammierung hat nur unveränderliche Variablen und kann daher seine Variablen nicht ändern. Aufgrund dieser Metaprogrammierung kann als eine Art von funktionaler Programmierung zu sehen.

Überprüfen Sie dieses Beispiel für Metaprogrammierung von Wikipedia aus. Es zeigt, wie Vorlagen können bei der Kompilierung Ausführen von Code verwendet werden. Deshalb zur Laufzeit Sie haben eine vorausberechnete Konstante ist.

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
    int x = Factorial<4>::value; // == 24
    int y = Factorial<0>::value; // == 1
}

Ein Ort, den ich Vorlagen verwenden, um meinen eigenen Code zu erstellen ist Policy-Klassen zu implementieren, wie von Andrei Alexandrescu in der modernen C ++ Entwurf beschrieben. Derzeit arbeite ich an einem Projekt, das eine Reihe von Klassen umfasst, die mit BEA \ h \ h \ h Oracle Tuxedo TP-Monitor in Wechselwirkung treten.

Eine Anlage, die Tuxedo bietet, ist Transaktions persistant Warteschlangen, so habe ich eine Klasse TpQueue, die mit der Warteschlange in Wechselwirkung tritt:

class TpQueue {
public:
   void enqueue(...)
   void dequeue(...)
   ...
}

Da jedoch die Warteschlange transaktional ist Ich muss entscheiden, welche Transaktionsverhalten ich will; Dies könnte separat außerhalb der TpQueue Klasse getan werden, aber ich denke, es ist noch deutlicher und weniger fehleranfällig, wenn jede TpQueue Instanz ihre eigene Politik auf Transaktionen hat. Also ich habe eine Reihe von TransactionPolicy Klassen wie:

class OwnTransaction {
public:
   begin(...)  // Suspend any open transaction and start a new one
   commit(..)  // Commit my transaction and resume any suspended one
   abort(...)
}

class SharedTransaction {
public:
   begin(...)  // Join the currently active transaction or start a new one if there isn't one
   ...
}

Und die TpQueue Klasse wird als

neu geschrieben
template <typename TXNPOLICY = SharedTransaction>
class TpQueue : public TXNPOLICY {
   ...
}

So innen TpQueue kann ich anfangen call (), abbrechen (), commit () nach Bedarf kann aber das Verhalten ändern, basierend auf die Art, wie ich die Instanz deklarieren:

TpQueue<SharedTransaction> queue1 ;
TpQueue<OwnTransaction> queue2 ;

Ich verwendete Vorlagen (mit Hilfe von Boost.Fusion) typsichere ganze Zahlen für eine Hyper Bibliothek zu erreichen, die ich entwickle. Ich habe eine (hyper) edge-ID und eine Eckenkennung von denen beide ganze Zahlen sind. Mit Vorlagen, Vertex und Hyperkante IDs wurden verschiedene Typen und eine Verwendung, wenn der andere erwartet wurde ein Fehler bei der Kompilierung erzeugt. Ersparte mir eine Menge Kopfschmerzen, die ich sonst mit Laufzeit-Debugging haben würde.

Hier ist ein Beispiel von einem realen Projekt. Ich habe Getter Funktionen wie folgt:

bool getValue(wxString key, wxString& value);
bool getValue(wxString key, int& value);
bool getValue(wxString key, double& value);
bool getValue(wxString key, bool& value);
bool getValue(wxString key, StorageGranularity& value);
bool getValue(wxString key, std::vector<wxString>& value);

Und dann eine Variante mit dem ‚Standard‘ Wert. Es gibt den Wert für Schlüssel, wenn es vorhanden ist, oder Standardwert, wenn dies nicht der Fall. Vorlage gespeichert mich mit 6 neuen Funktionen selbst erstellen.

template <typename T>
T get(wxString key, const T& defaultValue)
{
    T temp;
    if (getValue(key, temp))
        return temp;
    else
        return defaultValue;
}

Vorlagen ich regelmäßig verbrauchen, sind eine Vielzahl von Containerklassen, steigern intelligente Zeiger, scopeguards , einige STL-Algorithmen.

Szenarien, in denen ich Vorlagen geschrieben:

  • benutzerdefinierte Container
  • Speicherverwaltung, Implementierung Typsicherheit und CTOR / DTor Aufruf oben auf void * Verteilern
  • gemeinsame Umsetzung für Überlastungen wiht verschiedene Arten, z.

    bool ContainsNan (float *, int) bool ContainsNan (double *, int)

, die beide nur rufen Sie eine (lokal, versteckte) Hilfsfunktion

template <typename T>
bool ContainsNanT<T>(T * values, int len) { ... actual code goes here } ;

Spezielle Algorithmen, die unabhängig von der Art sind, solange die Art bestimmte Eigenschaften hat, zum Beispiel binäre Serialisierung.

template <typename T>
void BinStream::Serialize(T & value) { ... }

// to make a type serializable, you need to implement
void SerializeElement(BinStream & strean, Foo & element);
void DeserializeElement(BinStream & stream, Foo & element)

Im Gegensatz zu virtuellen Funktionen ermöglichen Vorlagen mehr Optimierungen stattfinden können.


Im Allgemeinen erlauben Vorlagen ein Konzept oder einen Algorithmus für eine Vielzahl von Arten zu implementieren, und haben die bereits bei der Kompilierung aufgelöst Unterschiede.

Wir verwenden COM und einen Zeiger auf ein Objekt übernehmen, die entweder eine andere Schnittstelle direkt oder über [IServiceProvider] ( http://msdn.microsoft.com/en-us/library/cc678965 (VS.85) .aspx) dies veranlasste mich, diese Helfer zu erstellen gussartige Funktion.

// Get interface either via QueryInterface of via QueryService
template <class IFace>
CComPtr<IFace> GetIFace(IUnknown* unk)
{
    CComQIPtr<IFace> ret = unk; // Try QueryInterface
    if (ret == NULL) { // Fallback to QueryService
        if(CComQIPtr<IServiceProvider> ser = unk)
            ser->QueryService(__uuidof(IFace), __uuidof(IFace), (void**)&ret);
    }
    return ret;
}

Ich verwende Vorlagen Funktion Objekttypen angeben. Ich schreibe oft Code, der eine Funktion Objekt als Argument - eine Funktion zu integrieren, eine Funktion zu optimieren, usw. - und ich finde Vorlagen bequemer als Vererbung. wie ein Integrator oder Optimierer - - Also mein Code ein Funktionsobjekt zu empfangen. hat einen Template-Parameter, um die Art der Funktion Objekts angeben, es arbeitet auf

Die offensichtlichen Gründe (wie verhindert Code-Duplizierung von auf verschiedenen Datentypen in Betrieb) zur Seite, gibt es diese wirklich cool Muster, das richtlinienbasierte Design genannt wird. Ich habe eine Frage zu Politik gefragt vs Strategien .

Nun, was über diese Funktion so geschickte ist. Betrachten Sie eine Schnittstelle für andere schreiben zu verwenden. Sie wissen, dass Ihre Schnittstelle verwendet werden, da es sich um ein Modul in der eigenen Domäne ist. Aber Sie wissen noch nicht, wie die Leute es verwenden werden. Policy-basiertes Design stärkt den Code für die spätere Wiederverwendung; es macht Sie unabhängig von Datentypen eine bestimmte Implementierung beruht auf. Der Code wird nur „schlürfte in“. : -)

Traits sind per se eine wunderbare Idee. Sie können auf ein Modell bestimmtes Verhalten, Daten und Typedata befestigen. Traits ermöglichen eine vollständige Parametrierung aller dieser drei Felder. Und das Beste daran, es ist ein sehr guter Weg, um Code wiederverwendbar zu machen.

Ich sah einmal den folgenden Code:

void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
   // three lines of code
   callFunctionGeneric1(c) ;
   // three lines of code
}

zehnmal wiederholt:

void doSomethingGeneric2(SomeClass * c, SomeClass & d)
void doSomethingGeneric3(SomeClass * c, SomeClass & d)
void doSomethingGeneric4(SomeClass * c, SomeClass & d)
// Etc

Jede Funktion mit den gleichen 6 Zeilen Code kopiert / eingefügt, und jedes Mal eine andere Funktion callFunctionGenericX mit der gleichen Nummer Suffix aufrufen.

Es gab keine Möglichkeit, die ganze Sache völlig zu umgestalten. Also hielt ich das Refactoring local.

änderte ich den Code auf diese Weise (aus dem Gedächtnis):

template<typename T>
void doSomethingGenericAnything(SomeClass * c, SomeClass & d, T t)
{
   // three lines of code
   t(c) ;
   // three lines of code
}

Und modifiziert, um den vorhandenen Code mit:

void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
   doSomethingGenericAnything(c, d, callFunctionGeneric1) ;
}

void doSomethingGeneric2(SomeClass * c, SomeClass & d)
{
   doSomethingGenericAnything(c, d, callFunctionGeneric2) ;
}

Etc.

Das ist etwas Entführung die Vorlage Sache, aber am Ende, ich denke, es ist besser als mit typedefed Funktionszeigern oder mit Hilfe von Makros zu spielen.

Ich habe persönlich das Merkwürdiger Recurring Template-Muster als Mittel zur Durchsetzung irgendeine Form von Top-Down-Design und Bottom-up-Implementierung verwendet. Ein Beispiel wäre eine Spezifikation für einen generischen Handler sein, wenn bestimmte Anforderungen an Form und Schnittstelle auf abgeleitete Typen bei der Kompilierung durchgesetzt werden. Es sieht etwa so aus:

template <class Derived>
struct handler_base : Derived {
  void pre_call() {
    // do any universal pre_call handling here
    static_cast<Derived *>(this)->pre_call();
  };

  void post_call(typename Derived::result_type & result) {
    static_cast<Derived *>(this)->post_call(result);
    // do any universal post_call handling here
  };

  typename Derived::result_type
  operator() (typename Derived::arg_pack const & args) {
    pre_call();
    typename Derived::result_type temp = static_cast<Derived *>(this)->eval(args);
    post_call(temp);
    return temp;
  };

};

So etwas wie dies dann dazu verwendet werden kann, um Ihre Handler aus dieser Vorlage stellen Sie sicher ableiten und Top-Down-Design durchzusetzen und dann für Bottom-up-Anpassung ermöglichen:

struct my_handler : handler_base<my_handler> {
  typedef int result_type; // required to compile
  typedef tuple<int, int> arg_pack; // required to compile
  void pre_call(); // required to compile
  void post_call(int &); // required to compile
  int eval(arg_pack const &); // required to compile
};

Dieses System erlaubt es, generische polymorphe Funktionen zu haben, die mit nur handler_base <> abgeleiteten Typen umgehen:

template <class T, class Arg0, class Arg1>
typename T::result_type
invoke(handler_base<T> & handler, Arg0 const & arg0, Arg1 const & arg1) {
  return handler(make_tuple(arg0, arg1));
};

Es ist bereits erwähnt worden, dass Sie Vorlagen als Policy-Klassen zu etwas tun können . Ich benutze dies viel.

ich sie auch nutzen, mit Hilfe von Immobilien Karten ( Boost-Website für weitere Informationen zu diesem ), um den Zugriff auf Daten in allgemeiner Weise. Dies gibt die Möglichkeit, die Art und Weise Sie Daten speichern, zu verändern, ohne jemals die Art und Weise zu ändern, die Sie es abrufen.

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