In Java können Sie generische Klassen definieren, die nur Typen akzeptieren, die die Klasse Ihrer Wahl erweitern, z.
public class ObservableList<T extends List> {
...
}
Dies erfolgt mit dem Schlüsselwort "erweitert".
Gibt es ein einfaches Äquivalent zu diesem Schlüsselwort in C ++?
Antworten:
Ich schlage vor, die statische Assert- Funktion
is_base_of
von Boost in Verbindung mit der Boost Type Traits-Bibliothek zu verwenden:In einigen anderen, einfacheren Fällen können Sie eine globale Vorlage einfach vorwärts deklarieren, sie jedoch nur für die gültigen Typen definieren (explizit oder teilweise spezialisieren):
[Minor EDIT 6/12/2013: Die Verwendung einer deklarierten, aber nicht definierten Vorlage führt zu Linker- und nicht zu Compiler-Fehlermeldungen.]
quelle
myBaseType
genau passen . Bevor Sie Boost schließen, sollten Sie wissen, dass das meiste davon nur Header-Vorlagencode ist. Daher fallen zur Laufzeit keine Speicher- oder Zeitkosten für Dinge an, die Sie nicht verwenden. Auch die speziellen Dinge, die Sie hier (BOOST_STATIC_ASSERT()
undis_base_of<>
) verwenden würden, können nur mit Deklarationen implementiert werden (dh ohne tatsächliche Definitionen von Funktionen oder Variablen), sodass sie weder Platz noch Zeit benötigen.static_assert(std::is_base_of<List, T>::value, "T must extend list")
.my_template<int> x;
oder zumy_template<float**> y;
überprüfen, ob der Compiler diese zulässt, und dann eine Variable deklarierenmy_template<char> z;
und überprüfen, ob dies nicht der Fall ist.Dies ist in C ++ normalerweise nicht gerechtfertigt, wie andere Antworten hier festgestellt haben. In C ++ neigen wir dazu, generische Typen basierend auf anderen Einschränkungen als "erbt von dieser Klasse" zu definieren. Wenn Sie das wirklich wollten, ist es in C ++ 11 ganz einfach und
<type_traits>
:Dies bricht jedoch viele der Konzepte, die Menschen in C ++ erwarten. Es ist besser, Tricks wie das Definieren eigener Merkmale anzuwenden.
observable_list
Möchte beispielsweise einen beliebigen Containertyp akzeptieren, der die typedefsconst_iterator
und einebegin
undend
member-Funktion hat, die zurückgibtconst_iterator
. Wenn Sie dies auf Klassen beschränken, die vonlist
einem Benutzer erben , der einen eigenen Typ hat, der nicht erbt,list
aber diese Mitgliedsfunktionen und typedefs bereitstellt, kann er Ihren nicht verwendenobservable_list
.Es gibt zwei Lösungen für dieses Problem. Eine davon besteht darin, nichts einzuschränken und sich auf das Tippen von Enten zu verlassen. Ein großer Nachteil dieser Lösung ist, dass sie eine große Menge an Fehlern enthält, die für Benutzer schwer zu verstehen sind. Eine andere Lösung besteht darin, Merkmale zu definieren, um den bereitgestellten Typ zu beschränken, um die Schnittstellenanforderungen zu erfüllen. Der große Nachteil dieser Lösung besteht darin, dass zusätzliches Schreiben erforderlich ist, was als ärgerlich angesehen werden kann. Die positive Seite ist jedoch, dass Sie Ihre eigenen Fehlermeldungen a la schreiben können
static_assert
.Der Vollständigkeit halber wird die Lösung für das obige Beispiel angegeben:
Im obigen Beispiel werden viele Konzepte gezeigt, die die Funktionen von C ++ 11 veranschaulichen. Einige Suchbegriffe für Neugierige sind verschiedene Vorlagen, SFINAE, Ausdruck SFINAE und Typmerkmale.
quelle
template<class T:list>
ein so beleidigendes Konzept ist. Danke für den Tipp.Die einfache Lösung, die noch niemand erwähnt hat, besteht darin, das Problem einfach zu ignorieren. Wenn ich versuche, einen
int
als Vorlagentyp in einer Funktionsvorlage zu verwenden, die eine Containerklasse wie einen Vektor oder eine Liste erwartet, wird ein Kompilierungsfehler angezeigt. Grob und einfach, aber es löst das Problem. Der Compiler versucht, den von Ihnen angegebenen Typ zu verwenden. Wenn dies fehlschlägt, wird ein Kompilierungsfehler generiert.Das einzige Problem dabei ist, dass die Fehlermeldungen, die Sie erhalten, schwierig zu lesen sind. Dies ist jedoch ein sehr üblicher Weg. Die Standardbibliothek ist voll von Funktions- oder Klassenvorlagen, die vom Vorlagentyp ein bestimmtes Verhalten erwarten und nichts tun, um die Gültigkeit der verwendeten Typen zu überprüfen.
Wenn Sie schönere Fehlermeldungen wünschen (oder wenn Sie Fälle abfangen möchten, die keinen Compilerfehler erzeugen, aber dennoch keinen Sinn ergeben), können Sie je nach Komplexität entweder die statische Zusicherung von Boost oder verwenden die Boost concept_check Bibliothek.
Mit einem aktuellen Compiler haben Sie ein eingebautes_in
static_assert
, das stattdessen verwendet werden könnte.quelle
T
und woher heißt dieser Code? Ohne irgendeinen Kontext habe ich keine Chance, dieses Code-Snippet zu verstehen. Aber was ich gesagt habe, ist wahr. Wenn Sie versuchen,toString()
einen Typ aufzurufen, der keinetoString
Mitgliedsfunktion hat, wird ein Kompilierungsfehler angezeigt.Wir können verwenden
std::is_base_of
undstd::enable_if
:(
static_assert
kann entfernt werden, die oben genannten Klassen können benutzerdefiniert implementiert oder aus Boost verwendet werden, wenn wir nicht referenzieren könnentype_traits
)quelle
Soweit ich weiß, ist dies derzeit in C ++ nicht möglich. Es ist jedoch geplant, dem neuen C ++ 0x-Standard eine Funktion namens "Konzepte" hinzuzufügen, die die von Ihnen gesuchte Funktionalität bietet. Dieser Wikipedia-Artikel über C ++ - Konzepte wird ausführlicher erläutert.
Ich weiß, dass dies Ihr unmittelbares Problem nicht behebt, aber es gibt einige C ++ - Compiler, die bereits damit begonnen haben, Funktionen aus dem neuen Standard hinzuzufügen. Daher ist es möglicherweise möglich, einen Compiler zu finden, der die Konzeptfunktion bereits implementiert hat.
quelle
static_assert
und SFINAE, wie die anderen Antworten zeigen. Das verbleibende Problem für jemanden, der aus Java oder C # oder Haskell (...) stammt, ist, dass der C ++ 20-Compiler keine Definitionsprüfung anhand der erforderlichen Konzepte durchführt, die Java und C # ausführen.Ich denke, alle vorherigen Antworten haben den Wald vor lauter Bäumen aus den Augen verloren.
Java-Generika sind nicht dasselbe wie Vorlagen . Sie verwenden die Typlöschung , bei der es sich um eine dynamische Technik handelt , und nicht den Kompilierungszeitpolymorphismus , bei dem es sich um eine statische Technik handelt . Es sollte offensichtlich sein, warum diese beiden sehr unterschiedlichen Taktiken nicht gut zusammenpassen.
Anstatt zu versuchen, ein Kompilierungszeitkonstrukt zu verwenden, um eine Laufzeitkonstruktion zu simulieren, schauen wir uns an, was
extends
tatsächlich funktioniert: Laut Stack Overflow und Wikipedia wird Extended verwendet, um Unterklassen anzuzeigen.C ++ unterstützt auch Unterklassen.
Sie zeigen auch eine Containerklasse an, die die Typlöschung in Form eines Generikums verwendet und erweitert wird, um eine Typprüfung durchzuführen. In C ++ müssen Sie die Löschmaschinerie selbst ausführen, was einfach ist: Erstellen Sie einen Zeiger auf die Oberklasse.
Lassen Sie es uns in ein typedef einwickeln, um die Verwendung zu vereinfachen, anstatt eine ganze Klasse zu erstellen, et voila:
typedef std::list<superclass*> subclasses_of_superclass_only_list;
Beispielsweise:
Nun scheint List eine Schnittstelle zu sein, die eine Art Sammlung darstellt. Eine Schnittstelle in C ++ wäre lediglich eine abstrakte Klasse, dh eine Klasse, die nur reine virtuelle Methoden implementiert. Mit dieser Methode können Sie Ihr Java-Beispiel problemlos in C ++ implementieren, ohne Konzepte oder Vorlagenspezialisierungen. Aufgrund der Suche nach virtuellen Tabellen ist die Leistung auch so langsam wie bei Generika im Java-Stil. Dies kann jedoch häufig ein akzeptabler Verlust sein.
quelle
Ein Äquivalent, das nur vom Typ List abgeleitete Typen T akzeptiert, sieht aus
quelle
Zusammenfassung: Tu das nicht.
Die Antwort von j_random_hacker zeigt Ihnen, wie das geht. Ich möchte jedoch auch darauf hinweisen, dass Sie dies nicht tun sollten . Der springende Punkt bei Vorlagen ist, dass sie jeden kompatiblen Typ akzeptieren können, und Einschränkungen des Java-Stiltyps brechen dies.
Javas Typeinschränkungen sind ein Fehler, keine Funktion. Sie sind vorhanden, weil Java die Löschung von Generika durchführt, sodass Java nicht herausfinden kann, wie Methoden allein anhand des Werts der Typparameter aufgerufen werden.
C ++ hat dagegen keine solche Einschränkung. Vorlagenparametertypen können beliebige Typen sein, die mit den Operationen kompatibel sind, mit denen sie verwendet werden. Es muss keine gemeinsame Basisklasse geben. Dies ähnelt Pythons "Duck Typing", wird jedoch zur Kompilierungszeit ausgeführt.
Ein einfaches Beispiel für die Leistungsfähigkeit von Vorlagen:
Diese Summenfunktion kann einen Vektor eines beliebigen Typs summieren, der die korrekten Operationen unterstützt. Es funktioniert sowohl mit Grundelementen wie int / long / float / double als auch mit benutzerdefinierten numerischen Typen, die den Operator + = überladen. Sie können diese Funktion sogar zum Verbinden von Zeichenfolgen verwenden, da diese + = unterstützen.
Es ist kein Ein- und Auspacken von Grundelementen erforderlich.
Beachten Sie, dass mit T () auch neue Instanzen von T erstellt werden. Dies ist in C ++ unter Verwendung impliziter Schnittstellen trivial, in Java mit Typbeschränkungen jedoch nicht wirklich möglich.
C ++ - Vorlagen haben zwar keine expliziten Typbeschränkungen, sind jedoch typsicher und werden nicht mit Code kompiliert, der die korrekten Vorgänge nicht unterstützt.
quelle
Dies ist in einfachem C ++ nicht möglich, aber Sie können Vorlagenparameter zur Kompilierungszeit durch Konzeptprüfung überprüfen, z. B. mithilfe der BCCL von Boost .
Ab C ++ 20 werden Konzepte zu einem offiziellen Merkmal der Sprache.
quelle
Stellen Sie sicher, dass abgeleitete Klassen die FooSecurity-Struktur erben und der Compiler an den richtigen Stellen verärgert wird.
quelle
Type::FooSecurity
wird in der Vorlagenklasse verwendet. Wenn die im Vorlagenargument übergebene Klasse dies nichtFooSecurity
getan hat , führt der Versuch, sie zu verwenden, zu einem Fehler. Es ist sicher, dass eine Klasse, die im Vorlagenargument übergeben wurde, keine FooSecurity hat, von der sie nicht abgeleitet istBase
.Verwendung des C ++ 20-Konzepts
https://en.cppreference.com/w/cpp/language/constraints cppreference gibt den Anwendungsfall für die Vererbung als explizites Konzeptbeispiel an:
Für mehrere Basen wird die Syntax vermutlich wie folgt lauten:
GCC 10 scheint es implementiert zu haben: https://gcc.gnu.org/gcc-10/changes.html und Sie können es als PPA unter Ubuntu 20.04 erhalten . https://godbolt.org/ Mein lokales GCC 10.1 hat es noch nicht erkannt
concept
, ist sich also nicht sicher, was los ist.quelle
Nein.
Je nachdem, was Sie erreichen möchten, gibt es möglicherweise geeignete (oder sogar bessere) Ersatzprodukte.
Ich habe einen STL-Code durchgesehen (unter Linux denke ich, dass er aus der Implementierung von SGI stammt). Es hat "Konzeptbehauptungen"; Wenn Sie beispielsweise einen Typ benötigen, der
*x
und versteht++x
, enthält die Konzeptzusicherung diesen Code in einer Nichtstun-Funktion (oder ähnlichem). Es erfordert etwas Overhead, daher ist es möglicherweise klug, es in ein Makro einzufügen, dessen Definition davon abhängt#ifdef debug
.Wenn die Unterklassenbeziehung wirklich das ist, worüber Sie wissen möchten, können Sie dies im Konstruktor behaupten
T instanceof list
(außer, dass sie in C ++ anders "geschrieben" ist). Auf diese Weise können Sie Ihren Weg aus dem Compiler testen, ohne ihn für Sie überprüfen zu können.quelle
Es gibt kein Schlüsselwort für solche Typprüfungen, aber Sie können Code einfügen, der zumindest ordnungsgemäß fehlschlägt:
(1) Wenn eine Funktionsvorlage nur Parameter einer bestimmten Basisklasse X akzeptieren soll, weisen Sie sie einer X-Referenz in Ihrer Funktion zu. (2) Wenn Sie Funktionen akzeptieren möchten, aber keine Grundelemente oder umgekehrt, oder wenn Sie Klassen auf andere Weise filtern möchten, rufen Sie eine (leere) Vorlagenhilfefunktion in Ihrer Funktion auf, die nur für die Klassen definiert ist, die Sie akzeptieren möchten.
Sie können (1) und (2) auch in Elementfunktionen einer Klasse verwenden, um diese Typprüfungen für die gesamte Klasse zu erzwingen.
Sie können es wahrscheinlich in ein intelligentes Makro einfügen, um Ihre Schmerzen zu lindern. :) :)
quelle
Nun, Sie könnten Ihre Vorlage mit folgendem Inhalt erstellen:
Dadurch wird die Einschränkung jedoch implizit, und Sie können nicht einfach alles angeben, das wie eine Liste aussieht. Es gibt andere Möglichkeiten, die verwendeten Containertypen einzuschränken, z. B. durch Verwendung bestimmter Iteratortypen, die nicht in allen Containern vorhanden sind. Dies ist jedoch eher eine implizite als eine explizite Einschränkung.
Nach meinem besten Wissen gibt es im aktuellen Standard kein Konstrukt, das die Java-Anweisung in vollem Umfang widerspiegeln würde.
Es gibt Möglichkeiten, die Typen einzuschränken, die Sie in einer von Ihnen geschriebenen Vorlage verwenden können, indem Sie bestimmte Typedefs in Ihrer Vorlage verwenden. Dadurch wird sichergestellt, dass die Kompilierung der Vorlagenspezialisierung für einen Typ, der dieses bestimmte typedef nicht enthält, fehlschlägt, sodass Sie bestimmte Typen selektiv unterstützen / nicht unterstützen können.
In C ++ 11 sollte die Einführung von Konzepten dies vereinfachen, aber ich denke nicht, dass es genau das tut, was Sie möchten.
quelle