Keine C ++ Liebe, wenn es um die "versteckten Funktionen von" Fragen geht? Ich dachte, ich würde es da rauswerfen. Was sind einige der versteckten Funktionen von C ++?
Keine C ++ Liebe, wenn es um die "versteckten Funktionen von" Fragen geht? Ich dachte, ich würde es da rauswerfen. Was sind einige der versteckten Funktionen von C ++?
Antworten:
Die meisten C ++ - Programmierer kennen den ternären Operator:
x = (y < 0) ? 10 : 20;
Sie erkennen jedoch nicht, dass es als Wert verwendet werden kann:
(a == 0 ? a : b) = 1;
Das ist eine Abkürzung für
if (a == 0)
a = 1;
else
b = 1;
Vorsichtig verwenden :-)
(value ? function1 : function2)()
.
function1
und function2
implizit in Funktionszeiger konvertiert werden und das Ergebnis implizit zurückkonvertiert wird.
Sie können URIs fehlerfrei in eine C ++ - Quelle einfügen. Beispielsweise:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
goto
, die C ++ hat). Alles, was nach zwei Schrägstrichen folgt, ist ein Kommentar. Daher mit http://stackoverflow.com
, http
ist ein Label (man könnte theoretisch schreiben goto http;
), und //stackoverflow.com
ist nur ein End-of-Line - Kommentar. Beide sind legales C ++, daher wird das Konstrukt kompiliert. Es macht natürlich nichts vage Nützliches.
goto http;
Folgt leider nicht wirklich der URL. :(
Zeigerarithmetik.
C ++ - Programmierer bevorzugen es, Zeiger zu vermeiden, da Fehler auftreten können.
Das coolste C ++, das ich je gesehen habe? Analoge Literale.
Ich stimme den meisten Posts dort zu: C ++ ist eine Multi-Paradigma-Sprache, daher sind die "versteckten" Funktionen, die Sie finden (außer "undefinierten Verhaltensweisen", die Sie unbedingt vermeiden sollten), eine clevere Verwendung von Einrichtungen.
Die meisten dieser Einrichtungen sind keine integrierten Funktionen der Sprache, sondern bibliotheksbasierte.
Das wichtigste ist das RAII , das von C ++ - Entwicklern aus der C-Welt oft jahrelang ignoriert wird. Das Überladen von Operatoren ist häufig eine missverstandene Funktion, die sowohl Array-ähnliches Verhalten (Indexoperator) als auch zeigerähnliche Operationen (intelligente Zeiger) und integrierte Operationen (Multiplikation von Matrizen) ermöglicht.
Die Verwendung von Ausnahmen ist oft schwierig, kann jedoch mit etwas Arbeit durch Ausnahmesicherheit wirklich robusten Code erzeugen (einschließlich Code, der nicht fehlschlägt oder über Commit-ähnliche Funktionen verfügt, die erfolgreich sind oder zu denen zurückgesetzt wird) seinen ursprünglichen Zustand).
Die bekannteste "versteckte" Funktion von C ++ ist die Vorlagen-Metaprogrammierung , da Sie Ihr Programm teilweise (oder vollständig) zur Kompilierungszeit anstatt zur Laufzeit ausführen lassen können. Dies ist jedoch schwierig, und Sie müssen die Vorlagen genau kennen, bevor Sie sie ausprobieren können.
Andere nutzen das multiple Paradigma, um "Programmierweisen" außerhalb des Vorfahren von C ++, dh C, zu erzeugen.
Durch die Verwendung von Funktoren können Sie Funktionen simulieren, mit der zusätzlichen Typensicherheit und dem Status. Mit dem Befehlsmuster können Sie die Codeausführung verzögern. Die meisten anderen Entwurfsmuster können einfach und effizient in C ++ implementiert werden, um alternative Codierungsstile zu erstellen, die nicht in der Liste der "offiziellen C ++ - Paradigmen" enthalten sein sollen.
Mithilfe von Vorlagen können Sie Code erstellen, der für die meisten Typen geeignet ist, auch nicht für den, an den Sie zuerst gedacht haben. Sie können auch die Typensicherheit erhöhen (wie bei einem automatisierten typsicheren Malloc / Realloc / Free). C ++ - Objektfunktionen sind sehr leistungsfähig (und daher gefährlich, wenn sie unachtsam verwendet werden), aber selbst der dynamische Polymorphismus hat seine statische Version in C ++: das CRTP .
Ich habe festgestellt, dass die meisten Bücher vom Typ " Effective C ++ " von Scott Meyers oder Bücher vom Typ " Exceptional C ++ " von Herb Sutter sowohl leicht zu lesen sind als auch eine Fülle von Informationen über bekannte und weniger bekannte Funktionen von C ++ enthalten.
Unter meinen bevorzugten ist eine, die die Haare eines jeden Java-Programmierers vor Entsetzen erheben sollte: In C ++ ist die objektorientierteste Möglichkeit, einem Objekt eine Funktion hinzuzufügen , die Funktion eines Nichtmitglieds anstelle eines Mitglieds. Funktion (dh Klassenmethode), weil:
In C ++ besteht die Schnittstelle einer Klasse sowohl aus ihren Member-Funktionen als auch aus den Nicht-Member-Funktionen im selben Namespace
Nicht-Freund-Nicht-Mitglied-Funktionen haben keinen privilegierten Zugriff auf die interne Klasse. Wenn Sie also eine Member-Funktion über eine Nicht-Member-Non-Friend-Funktion verwenden, wird die Kapselung der Klasse geschwächt.
Dies überrascht selbst erfahrene Entwickler immer wieder.
(Quelle: Unter anderem Herb Sutters Online-Guru der Woche Nr. 84: http://www.gotw.ca/gotw/084.htm )
Ein Sprachmerkmal, das ich für etwas versteckt halte, weil ich während meiner gesamten Schulzeit noch nie davon gehört hatte, ist der Namespace-Alias. Ich wurde erst darauf aufmerksam gemacht, als ich in der Boost-Dokumentation auf Beispiele dafür stieß. Jetzt, da ich davon weiß, können Sie es natürlich in jeder Standard-C ++ - Referenz finden.
namespace fs = boost::filesystem;
fs::path myPath( strPath, fs::native );
using
.
Im init-Teil einer for
Schleife können nicht nur Variablen deklariert werden , sondern auch Klassen und Funktionen.
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}
Dies ermöglicht mehrere Variablen unterschiedlichen Typs.
Der Array-Operator ist assoziativ.
A [8] ist ein Synonym für * (A + 8). Da Addition assoziativ ist, kann dies als * (8 + A) umgeschrieben werden, was ein Synonym für ..... 8 [A] ist.
Du hast nicht nützlich gesagt ... :-)
A
überhaupt keine Rolle. Wenn zum Beispiel A
ein sind char*
, würde der Code immer noch gültig sein.
Eine Sache, die wenig bekannt ist, ist, dass Gewerkschaften auch Vorlagen sein können:
template<typename From, typename To>
union union_cast {
From from;
To to;
union_cast(From from)
:from(from) { }
To getTo() const { return to; }
};
Und sie können auch Konstruktoren und Elementfunktionen haben. Nur nichts, was mit Vererbung zu tun hat (einschließlich virtueller Funktionen).
From
und To
entsprechend gesetzt und verwendet werden. Eine solche Vereinigung kann jedoch mit definiertem Verhalten verwendet werden (wobei To
es sich um ein Array von Zeichen ohne Vorzeichen oder eine Struktur handelt, mit der eine Anfangssequenz geteilt wird From
). Selbst wenn Sie es undefiniert verwenden, kann es für Arbeiten auf niedriger Ebene nützlich sein. Auf jeden Fall ist dies nur ein Beispiel für eine Gewerkschaftsvorlage - es kann andere Verwendungszwecke für eine Vorlagenvereinigung geben.
C ++ ist ein Standard, es sollte keine versteckten Funktionen geben ...
C ++ ist eine Multi-Paradigmen-Sprache. Sie können Ihr letztes Geld darauf setzen, dass es versteckte Funktionen gibt. Ein Beispiel von vielen: Template-Metaprogrammierung . Niemand im Normungsausschuss beabsichtigte, dass es eine Turing-vollständige Subsprache geben sollte, die zur Kompilierungszeit ausgeführt wird.
Eine weitere versteckte Funktion, die in C nicht funktioniert, ist die Funktionalität des unären +
Operators. Sie können es verwenden, um alle möglichen Dinge zu fördern und zu verfallen
+AnEnumeratorValue
Und Ihr Enumeratorwert, der zuvor seinen Aufzählungstyp hatte, hat jetzt den perfekten Ganzzahltyp, der zu seinem Wert passt. Manuell würde man diesen Typ kaum kennen! Dies ist beispielsweise erforderlich, wenn Sie einen überladenen Operator für Ihre Aufzählung implementieren möchten.
Sie müssen eine Klasse verwenden, die einen statischen Initialisierer innerhalb der Klasse ohne Definition außerhalb der Klasse verwendet, aber manchmal keine Verknüpfung herstellt. Der Operator kann dabei helfen, eine temporäre Datei zu erstellen, ohne Annahmen oder Abhängigkeiten von ihrem Typ zu treffen
struct Foo {
static int const value = 42;
};
// This does something interesting...
template<typename T>
void f(T const&);
int main() {
// fails to link - tries to get the address of "Foo::value"!
f(Foo::value);
// works - pass a temporary value
f(+Foo::value);
}
Möchten Sie zwei Zeiger an eine Funktion übergeben, aber es funktioniert einfach nicht? Der Bediener kann helfen
// This does something interesting...
template<typename T>
void f(T const& a, T const& b);
int main() {
int a[2];
int b[3];
f(a, b); // won't work! different values for "T"!
f(+a, +b); // works! T is "int*" both time
}
Die Lebensdauer von Provisorien, die an konstante Referenzen gebunden sind, ist eine, die nur wenige Menschen kennen. Zumindest ist es mein Lieblingswissen in C ++, von dem die meisten Leute nichts wissen.
const MyClass& x = MyClass(); // temporary exists as long as x is in scope
Eine nette Funktion, die nicht oft verwendet wird, ist der funktionsweite Try-Catch-Block:
int Function()
try
{
// do something here
return 42;
}
catch(...)
{
return -1;
}
Die Hauptverwendung wäre, eine Ausnahme in eine andere Ausnahmeklasse zu übersetzen und erneut zu werfen oder zwischen Ausnahmen und einer auf Rückgabe basierenden Fehlercodebehandlung zu übersetzen.
return
aus dem Catch-Block von Function Try nur erneut werfen können.
Viele kennen die identity
/ id
metafunktion, aber es gibt einen guten Anwendungsfall für Fälle ohne Vorlage: Einfaches Schreiben von Erklärungen:
// void (*f)(); // same
id<void()>::type *f;
// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);
// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];
// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;
Es hilft sehr, C ++ - Deklarationen zu entschlüsseln!
// boost::identity is pretty much the same
template<typename T>
struct id { typedef T type; };
template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;
-> pointer<function<void,int>> f(pointer<function<void,void>>);
oder pointer<void(int)> f(pointer<void()>);
oderfunction<pointer<function<void,int>>,pointer<function<void,void>>> f;
Eine ziemlich versteckte Funktion ist, dass Sie Variablen innerhalb einer if-Bedingung definieren können und ihr Bereich sich nur über die if- und else-Blöcke erstreckt:
if(int * p = getPointer()) {
// do something
}
Einige Makros verwenden dies, um beispielsweise einen "gesperrten" Bereich wie diesen bereitzustellen:
struct MutexLocker {
MutexLocker(Mutex&);
~MutexLocker();
operator bool() const { return false; }
private:
Mutex &m;
};
#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else
void someCriticalPath() {
locked(myLocker) { /* ... */ }
}
Auch BOOST_FOREACH benutzt es unter der Haube. Um dies zu vervollständigen, ist es nicht nur in einem if möglich, sondern auch in einem Switch:
switch(int value = getIt()) {
// ...
}
und in einer while-Schleife:
while(SomeThing t = getSomeThing()) {
// ...
}
(und auch in einem Zustand). Aber ich bin mir nicht sicher, ob das alles so nützlich ist :)
if((a = f()) == b) ...
, aber diese Antwort deklariert tatsächlich eine Variable in der Bedingung.
for(...; int i = foo(); ) ...;
dies durch den Körper gehen, solange i
es wahr ist, und es jedes Mal neu initialisieren. Die Schleife, die Sie zeigen, demonstriert einfach eine Variablendeklaration, aber keine Variablendeklaration, die gleichzeitig als Bedingung fungiert :)
Manchmal verwenden Sie den Kommaoperator gültig, möchten aber sicherstellen, dass kein benutzerdefinierter Kommaoperator in die Quere kommt, weil Sie sich beispielsweise auf Sequenzpunkte zwischen der linken und rechten Seite verlassen oder sicherstellen möchten, dass nichts den gewünschten stört Aktion. Hier void()
kommt das Spiel ins Spiel:
for(T i, j; can_continue(i, j); ++i, void(), ++j)
do_code(i, j);
Ignoriere die Platzhalter, die ich für die Bedingung und den Code angegeben habe. Was wichtig ist void()
, ist das , was den Compiler dazu zwingt, den eingebauten Kommaoperator zu verwenden. Dies kann manchmal auch bei der Implementierung von Merkmalsklassen hilfreich sein.
Array-Initialisierung im Konstruktor. Zum Beispiel in einer Klasse, wenn wir ein Array von int
as haben:
class clName
{
clName();
int a[10];
};
Wir können alle Elemente im Array auf ihre Standardeinstellung (hier alle Elemente des Arrays auf Null) im Konstruktor wie folgt initialisieren:
clName::clName() : a()
{
}
Oooh, ich kann stattdessen eine Liste mit Tierhassen erstellen:
Auf der positiven Seite
Sie können ohne undefiniertes Verhalten und mit erwarteter Semantik auf geschützte Daten und Funktionsmitglieder jeder Klasse zugreifen. Lesen Sie weiter, um zu sehen, wie. Lesen Sie dazu auch den Fehlerbericht .
Normalerweise verbietet Ihnen C ++ den Zugriff auf nicht statisch geschützte Elemente des Objekts einer Klasse, selbst wenn diese Klasse Ihre Basisklasse ist
struct A {
protected:
int a;
};
struct B : A {
// error: can't access protected member
static int get(A &x) { return x.a; }
};
struct C : A { };
Das ist verboten: Sie und der Compiler wissen nicht, worauf die Referenz tatsächlich verweist. Es könnte sich um ein C
Objekt handeln. In diesem Fall B
hat die Klasse kein Geschäft und keine Ahnung von ihren Daten. Ein solcher Zugriff wird nur gewährt, wenn x
auf eine abgeleitete oder von dieser abgeleitete Klasse verwiesen wird. Und es könnte jedem beliebigen Code erlauben, jedes geschützte Mitglied zu lesen, indem nur eine "Wegwerf" -Klasse gebildet wird, die Mitglieder vorliest, zum Beispiel std::stack
:
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
// error: stack<int>::c is protected
return s.c;
}
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}
Wie Sie sehen, würde dies sicherlich viel zu viel Schaden anrichten. Aber jetzt erlauben Mitgliedszeiger, diesen Schutz zu umgehen! Der entscheidende Punkt ist, dass der Typ eines Mitgliedszeigers an die Klasse gebunden ist, die das Mitglied tatsächlich enthält - nicht an die Klasse, die Sie bei der Übernahme der Adresse angegeben haben. Dies ermöglicht es uns, die Überprüfung zu umgehen
struct A {
protected:
int a;
};
struct B : A {
// valid: *can* access protected member
static int get(A &x) { return x.*(&B::a); }
};
struct C : A { };
Und natürlich funktioniert es auch mit dem std::stack
Beispiel.
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
return s.*(pillager::c);
}
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}
Mit einer using-Deklaration in der abgeleiteten Klasse, die den Mitgliedsnamen öffentlich macht und auf das Mitglied der Basisklasse verweist, wird dies noch einfacher.
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
using std::stack<int>::c;
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = s.*(&pillager::c);
}
Eine weitere versteckte Funktion ist, dass Sie Klassenobjekte aufrufen können, die in Funktionszeiger oder Referenzen konvertiert werden können. Die Überlastungsauflösung erfolgt anhand des Ergebnisses, und die Argumente werden perfekt weitergeleitet.
template<typename Func1, typename Func2>
class callable {
Func1 *m_f1;
Func2 *m_f2;
public:
callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
operator Func1*() { return m_f1; }
operator Func2*() { return m_f2; }
};
void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }
int main() {
callable<void(int), void(long)> c(foo, bar);
c(42); // calls foo
c(42L); // calls bar
}
Diese werden als "Ersatzanruffunktionen" bezeichnet.
Versteckte Funktionen:
Wenn eine Funktion eine Ausnahme auslöst, die nicht in ihren Ausnahmespezifikationen aufgeführt ist, die Funktion jedoch std::bad_exception
in ihrer Ausnahmespezifikation enthält, wird die Ausnahme in konvertiert std::bad_exception
und automatisch ausgelöst. Auf diese Weise wissen Sie zumindest, dass ein bad_exception
geworfen wurde. Lesen Sie hier mehr .
Funktionsversuchsblöcke
Das Template-Schlüsselwort zur Unterscheidung von Typedefs in einer Klassenvorlage. Wenn der Name eines Mitglied Template - Spezialisierung erscheint nach .
, ->
oder ::
Operator, und dieser Name hat explizit qualifizierte Template - Parameter, Präfix das Element Template - Namen mit der Keyword - Vorlage. Lesen Sie hier mehr .
Die Standardeinstellungen für Funktionsparameter können zur Laufzeit geändert werden. Lesen Sie hier mehr .
A[i]
funktioniert so gut wie i[A]
Temporäre Instanzen einer Klasse können geändert werden! Eine nicht konstante Mitgliedsfunktion kann für ein temporäres Objekt aufgerufen werden. Beispielsweise:
struct Bar {
void modify() {}
}
int main (void) {
Bar().modify(); /* non-const function invoked on a temporary. */
}
Lesen Sie hier mehr .
Wenn vor und nach dem Operatorausdruck :
ternary ( ?:
) zwei verschiedene Typen vorhanden sind , ist der resultierende Typ des Ausdrucks der allgemeinste der beiden. Beispielsweise:
void foo (int) {}
void foo (double) {}
struct X {
X (double d = 0.0) {}
};
void foo (X) {}
int main(void) {
int i = 1;
foo(i ? 0 : 0.0); // calls foo(double)
X x;
foo(i ? 0.0 : x); // calls foo(X)
}
map::operator[]
Erstellt einen Eintrag, wenn der Schlüssel fehlt, und gibt den Verweis auf den standardmäßig erstellten Eintragswert zurück. So können Sie schreiben:
map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
s.assign(...);
}
cout << s;
Ich bin erstaunt, wie viele C ++ - Programmierer das nicht wissen.
.find()
.
const map::operator[]
generiert Fehlermeldungen"
Durch das Einfügen von Funktionen oder Variablen in einen namenlosen Namespace wird die Verwendung von static
eingeschränkt, um sie auf den Dateibereich zu beschränken.
static
globale Geltungsbereich in keiner Weise veraltet. (Als Referenz: C ++ 03 §D.2)
static
use sollte nur innerhalb eines Klassentyps oder einer Funktion verwendet werden.
Das Definieren gewöhnlicher Freundfunktionen in Klassenvorlagen erfordert besondere Aufmerksamkeit:
template <typename T>
class Creator {
friend void appear() { // a new function ::appear(), but it doesn't
… // exist until Creator is instantiated
}
};
Creator<void> miracle; // ::appear() is created at this point
Creator<double> oops; // ERROR: ::appear() is created a second time!
In diesem Beispiel erstellen zwei verschiedene Instanziierungen zwei identische Definitionen - eine direkte Verletzung des ODR
Wir müssen daher sicherstellen, dass die Vorlagenparameter der Klassenvorlage im Typ einer in dieser Vorlage definierten Friend-Funktion angezeigt werden (es sei denn, wir möchten mehr als eine Instanziierung einer Klassenvorlage in einer bestimmten Datei verhindern, dies ist jedoch eher unwahrscheinlich). Wenden wir dies auf eine Variation unseres vorherigen Beispiels an:
template <typename T>
class Creator {
friend void feed(Creator<T>*){ // every T generates a different
… // function ::feed()
}
};
Creator<void> one; // generates ::feed(Creator<void>*)
Creator<double> two; // generates ::feed(Creator<double>*)
Haftungsausschluss: Ich habe diesen Abschnitt aus C ++ - Vorlagen eingefügt : The Complete Guide / Section 8.4
Wenig bekannt, aber der folgende Code ist in Ordnung
void f() { }
void g() { return f(); }
Aswell wie der folgende seltsam aussehende
void f() { return (void)"i'm discarded"; }
Wenn Sie dies wissen, können Sie in einigen Bereichen davon profitieren. Ein Beispiel: void
Funktionen können keinen Wert zurückgeben, aber Sie können auch nicht einfach nichts zurückgeben, da sie möglicherweise mit nicht ungültig instanziiert werden. Anstatt den Wert in einer lokalen Variablen zu speichern, die einen Fehler verursacht void
, geben Sie einfach einen Wert direkt zurück
template<typename T>
struct sample {
// assume f<T> may return void
T dosomething() { return f<T>(); }
// better than T t = f<T>(); /* ... */ return t; !
};
Lesen Sie eine Datei in einen Vektor von Zeichenfolgen:
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());
- fehlende Klammern nach dem zweiten Parameter
Sie können Bitfelder vorlegen.
template <size_t X, size_t Y>
struct bitfield
{
char left : X;
char right : Y;
};
Ich habe mir noch keinen Zweck dafür ausgedacht, aber es hat mich verdammt noch mal überrascht.
Eine der interessantesten Grammatiken aller Programmiersprachen.
Drei dieser Dinge gehören zusammen und zwei sind etwas ganz anderes ...
SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));
Alle außer dem dritten und fünften definieren ein SomeType
Objekt auf dem Stapel und initialisieren es (mit u
in den ersten beiden Fällen und dem Standardkonstruktor im vierten. Der dritte deklariert eine Funktion, die keine Parameter akzeptiert und a zurückgibt SomeType
. Der fünfte deklariert ähnlich eine Funktion , die einen Parameter mit Werten vom Typ nimmt SomeType
benannt u
.
Vorwärtserklärungen loswerden:
struct global
{
void main()
{
a = 1;
b();
}
int a;
void b(){}
}
singleton;
Schreiben von switch-Anweisungen mit ?: Operatoren:
string result =
a==0 ? "zero" :
a==1 ? "one" :
a==2 ? "two" :
0;
Alles in einer einzigen Zeile erledigen:
void a();
int b();
float c = (a(),b(),1.0f);
Nullstellen von Strukturen ohne Memset:
FStruct s = {0};
Winkel- und Zeitwerte normalisieren / umbrechen:
int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150
Verweisen von Referenzen:
struct ref
{
int& r;
ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;
FStruct s = {};
ist noch kürzer.
main
? Ich würde vorschlagen global().main();
und einfach den Singleton vergessen ( Sie können einfach mit dem temporären arbeiten, wodurch die Lebensdauer verlängert wird )
Der ternäre bedingte Operator ?:
verlangt, dass sein zweiter und dritter Operand "akzeptable" Typen haben (informell sprechen). Diese Anforderung hat jedoch eine Ausnahme (Wortspiel beabsichtigt): Entweder der zweite oder der dritte Operand kann ein Wurfausdruck sein (der Typ hatvoid
), unabhängig vom Typ des anderen Operanden.
Mit anderen Worten, man kann die folgenden genau gültigen C ++ - Ausdrücke mit dem ?:
Operator schreiben
i = a > b ? a : throw something();
Übrigens ist die Tatsache, dass throw expression tatsächlich ein Ausdruck (vom Typ void
) und keine Anweisung ist, ein weiteres wenig bekanntes Merkmal der C ++ - Sprache. Dies bedeutet unter anderem, dass der folgende Code vollkommen gültig ist
void foo()
{
return throw something();
}
obwohl es nicht viel Sinn macht, es auf diese Weise zu tun (vielleicht ist dies in einem generischen Vorlagencode nützlich).
Die Dominanzregel ist nützlich, aber wenig bekannt. Selbst wenn sich die Mitglieder in einem nicht eindeutigen Pfad durch ein Gitter der Basisklasse befinden, ist die Namenssuche für ein teilweise verstecktes Mitglied eindeutig, wenn das Mitglied zu einer virtuellen Basisklasse gehört:
struct A { void f() { } };
struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };
// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };
Ich habe dies verwendet, um eine Ausrichtungsunterstützung zu implementieren , die mithilfe der Dominanzregel automatisch die strengste Ausrichtung ermittelt.
Dies gilt nicht nur für virtuelle Funktionen, sondern auch für typedef Namen, statische / nicht virtuelle Mitglieder und alles andere. Ich habe gesehen, dass es verwendet wurde, um überschreibbare Merkmale in Metaprogrammen zu implementieren.
struct C
in Ihr Beispiel aufgenommen haben ...? Prost.