2016-06-25 16 views
2

Angenommen, ich baue eine verkettete Liste (die reale Datenstruktur ist völlig anders, aber eine verknüpfte Liste reicht für die Frage), dessen Knoten aussehenOpaque Zeiger mit Schablonen Mitglieder auf struct

template <typename T> 
struct node 
{ 
    struct node<T> *next; 
    T data; 
}; 

Für meine Daten Struktur, ich habe viele Funktionen mit Rückgabetyp struct node *, und ich möchte der Benutzer diesen Typ als undurchsichtig behandeln. Im Beispiel der verknüpften Liste könnte eine solche Funktion beispielsweise get_next(struct node<T> *n) oder insert_after(struct node<T> *x, struct node<T> *y) sein. Nur sehr wenige Funktionen, nämlich diejenigen, die node s zuweisen oder ihr data Feld bekommen, müssen etwas über T wissen.

Gibt es eine schönere Möglichkeit, "T" zu ignorieren und den Benutzer nur mit etwas wie einem typedef struct node * opaque_handle für die Funktionen interagieren zu lassen, die sich nie um T kümmern müssen? Meine Bauchreaktion, die von C kommt, ist nur zu und von void* zu werfen, aber das klingt nicht sehr elegant.

Edit: CygnusX1 des comment hat mich überzeugt, dass ich zur gleichen Zeit für zu viele Garantien vom Typ System bin gefragt, die ich versuche, zu viele dieser Garantien zu umgehen. Ich werde darauf zurückfallen, Tvoid * auf Kosten von Gießen und Indirektion zu sein.

+0

vielleicht nicht elegant klingt, aber es weit Praxis verwendet wird. lesen Sie zum Beispiel über [pimpl] (https://en.wikipedia.org/wiki/Opaque_pointer).Haben Sie eine Forward-Deklaration 'struct T' im öffentlichen Header und verwenden Sie' T * 'in' struct node' (muss in diesem Fall keine Vorlage sein) – mvidelgauz

+0

Gibt es eine spezielle Notwendigkeit, Ihre Datenstruktur so zu gestalten, dass macht den 'node ' Typ? Warum modellieren Sie es nicht nach den anderen Containern im Standard (Benutzeroberflächenfunktionen geben 'T' anstelle von' Knoten 'zurück)? – user2296177

+0

Sie möchten vielleicht sehen, wie Sammlungen [STL] (http://www.cplusplus.com/reference/list/list/) mit dem Problem umgehen. Sie geben dem Endbenutzer nicht den Zugriff auf die Implementierungsstruktur, wie z. 'Knoten'. Ihre Schnittstelle enthält eine Zusammenfassung, die für Endbenutzertypen wie "Iterator", "Werttyp", "Referenz" und so weiter verständlich ist. –

Antwort

1

Während Sie sich nicht interessieren, was T ist, möchten Sie es am meisten von einem anderen Typ unterscheiden - sagen Sie U, nicht wahr? Sie wollen wahrscheinlich die folgenden um einen Fehler zu erhöhen:

node<T>* elem1 = ... 
node<U>* elem2 = ... 
elem1 = elem2 

Es gibt ein paar Möglichkeiten, um Ihren Code einfacher zu machen, ohne die Typprüfung zu verzichten oder Laufzeit perforamce:

  • Wenn Sie C verwenden + +11 betrachten auto anstelle von ausdrücklich auf die Art der Benennung, wenn Ihre Funktionen
  • Wenn node<T> in Ihrem Code sehr häufig, können Sie festlegen, einen globalen Rahmen typedef

Beachten Sie auch, dass im Zusammenhang mit der node<T> Definition eine einfache node (ohne Vorlagenargumente) erlaubt ist.

Wenn Sie den Inhalt des node wirklich ausblenden möchten, können Sie das pimpl Muster wie von mvidelgauz vorgeschlagen implementieren.

+0

Zum ersten Teil Ihrer Antwort: Nun, für * einige * Funktionen, ja. Aber für viele der Funktionen (wie das Einfügen eines neuen, bereits zugewiesenen Knotens in das Beispiel der verknüpften Liste) ist es absolut nicht notwendig, etwas über die Felder in der Struktur zu wissen, die der ersten folgen. Viele meiner Funktionen müssen nur wissen, dass die Struktur mit einem Zeiger auf einen Knoten beginnt. 'T' kann eine Million Bytes verrückten Kram sein für alles was sie interessiert. – gspr

+0

In Bezug auf die 'typedef ': Aber ich kann' typedef Strukturknoten * foo' nicht tun, kann ich? Wenn ich könnte, würden fast alle meine Funktionen den "foo" -Typ übernehmen und zurückgeben, und würden funktionieren, wenn ich weiß, dass "foo" ein Zeiger auf einen Knoten 'ist und es mir egal ist, was' T' ist , denn ich brauche nur das erste Feld, das immer ein Zeiger auf einen anderen 'Knoten ' "ist. – gspr

+0

@gspr Sie müssen immer noch wissen, dass das zweite Element, das Sie einfügen, vom gleichen Typ ist wie das erste - selbst wenn es Millionen Bytes an verrückten Sachen sind. – CygnusX1

0

Wenn Sie boost verwenden können, kann boost :: any oder boost :: variant helfen, heterogene Container zu implementieren.

ist so etwas wie das, was Sie nach ?:

#include <iostream> 
#include <boost/any.hpp> 
#include <list> 

using Collection = std::list<boost::any>; 
using Node = Collection::iterator; 

static Collection anys; 

template<typename T> 
Node insert_after(T const& obj, Node start_pos = anys.end()) 
{ 
    return anys.insert(start_pos, boost::any(obj)); 
} 

void print_type(boost::any const& a) 
{ 
    if (a.type() == typeid(int)) { std::cout << "int" << std::endl; } 
    else if (a.type() == typeid(float)) { std::cout << "float" << std::endl; } 
} 

int main() 
{ 
    const auto node1 = insert_after(int(1)); 
    const auto node2 = insert_after(float(2.57)); 
    const auto node3 = insert_after(int(3)); 

    std::cout << boost::any_cast<int>(*node1) << std::endl; 
    std::cout << boost::any_cast<float>(*node2) << std::endl; 
    std::cout << boost::any_cast<int>(*node3) << std::endl; 

    print_type(*node1); 
    print_type(*node2); 
    print_type(*node3); 

    return 0; 
} 

Ausgänge:

1 
2.57 
3 
int 
float 
int 
+0

Er möchte keinen heterogenen Container, er will nur die Tatsache verdecken, dass seine Knoten 'Knoten ' mit einem anderen Typ sind, der keine Information über 'T' enthält. – user2296177

+0

Danke für die Antwort, aber der meiste Boost ist so schwarzmagisch für mich, dass ich versuche es zu vermeiden, es sei denn absolut notwendig :-) – gspr

+0

@ user2296177 Ah, ich dachte basierend auf den anderen Kommentaren, dass OP eine Liste erstellen wollte Knoten können verschiedene T enthalten, sorry. – mkal