Zusammenfassung der neuen Funktionen von C++

(Intelligente Zeiger, einige Schlüsselwörter, automatische Typableitung, perfekte Weiterleitung der R-Wert-Referenz-Bewegungssemantik, Listeninitialisierung, std::function & std::bind & Lambda-Ausdrücke machen Rückrufe bequemer, C++ 11 führt Parallelität ein. Viel Gutes Dinge, einschließlich:
std::thread bezogen auf
std::mutex bezogen auf
std::lock bezogen auf
std::atomic bezogen auf
std::call_once bezogen auf
volatile bezogen auf
std::condition_variable bezogen auf
std::future bezogen auf
async bezogen)

Fassen Sie drei Aspekte zusammen: Der eine ist bequemer und der andere effizienter und sicherer . Praktische Schlüsselwörter wie auto für i:arr, std::function. Effektiv wie intelligente Zeiger, perfekte Weiterleitung mit Bewegungssemantik.
Der dritte ist die Thread-Unterstützung. Parallelitätsunterstützung. Atomare Variablen, Sperren, Bedingungsvariablen usw. Erhalten Sie asynchron das zukünftige Ergebnis usw.

9. Smart-Pointer-Problem
Ein Smart-Pointer ist eine Klasse , die zum Speichern von Zeigern auf dynamisch zugewiesene Objekte verwendet wird und für die automatische Freigabe dynamisch zugewiesener Objekte verantwortlich ist, um Heap-Speicherlecks zu verhindern. Dynamisch zugewiesene Ressourcen werden zur Verwaltung an ein Klassenobjekt übergeben. Wenn der Deklarationszyklus des Klassenobjekts endet, wird der Destruktor automatisch aufgerufen, um die Ressource freizugeben.
Obwohl die Verwendung von Neu- und Löschoperatoren für die dynamische Speicherverwaltung die Effizienz des Programms verbessern kann, ist sie auch sehr anfällig für Probleme: Vergessen, den Speicher freizugeben, was zu Speicherlecks führt, Freigabe, wenn noch ein Zeiger auf den Speicher vorhanden ist, Dies führt zu illegalen Verweisen auf den Speicherzeiger. Nach Auftreten einer Ausnahme tritt das Programm in den Catch ein und vergisst, den Speicher freizugeben. Intelligente Zeiger sind eine Kapselung gewöhnlicher Zeiger mithilfe der RAII-Technologie. Die RAII-Technologie, auch bekannt als „ Ressourcenbeschaffung ist Initialisierung “, ist eine C++-Sprachsprache zur Verwaltung von Ressourcen und zur Vermeidung von Lecks. Verwenden Sie auf dem Stapel gespeicherte lokale Objekte (Klassen), um die Ressourcenzuweisung und -initialisierung zu kapseln, die Ressourcenzuweisung und -initialisierung im Konstruktor abzuschließen und die Ressourcenbereinigung im Destruktor abzuschließen , wodurch eine korrekte Initialisierung und Ressourcenfreigabe sichergestellt werden kann. Ein lokales Objekt bezieht sich auf ein auf dem Stapel gespeichertes Objekt, dessen Lebenszyklus vom Betriebssystem ohne manuellen Eingriff verwaltet wird.
Die nach der C ++ 11-Version bereitgestellten intelligenten Zeiger sind in der Header-Datei enthalten. Dies sind die Code-Implementierungen für intelligente Zeiger auto_ptr, shared_ptr, unique_ptr und schwach_ptr
: Verwenden Sie zwei Klassen, um die Funktion intelligenter Zeiger ** zu realisieren, eine davon ist die Referenz Zählklasse und die andere ist eine Zeigerklasse. **

//  引用计数器类  用于存储指向同一对象的指针数
template<typename T>
class Counter
{
    
    
private:
	//  数据成员
	T *ptr;    //  对象指针
	int cnt;   //  引用计数器
 
	//  友元类声明
	template<typename T>
	friend class SmartPtr;
 
	//  成员函数
	//  构造函数
	Counter(T *p)   //  p为指向动态分配对象的指针
	{
    
    
		ptr = p;
		cnt = 1;
	}
	//  析构函数
	~Counter()
	{
    
    
		delete ptr;
	}
};
//  智能指针类  
template<typename T>
class SmartPtr
{
    
    
private:
	//  数据成员
	Counter<T> *ptr_cnt;  //  
 
public:
 
	//  成员函数
	//  普通构造函数  初始化计数类
	SmartPtr(T *p)
	{
    
    
		ptr_cnt = new Counter<T>(p);
	}
	//  拷贝构造函数 计数器加1
	SmartPtr(const SmartPtr &other)
	{
    
    
		ptr_cnt = other.ptr_cnt;
		ptr_cnt->cnt++;
	}
	//  赋值运算符重载函数
	SmartPtr &operator=(const SmartPtr &rhs)
	{
    
    
		ptr_cnt = rhs->ptr_cnt;
		rhs.ptr_cnt->cnt++; 增加右操作数的计数器
		ptr_cnt->cnt--; 左操作数计数器减1
		if (ptr_cnt->cnt == 0)
			delete ptr_cnt;
		return *this;
	}
	//  解引用运算符重载函数
	T &operator*()
	{
    
    
		return *(ptr_cnt->cnt);
	}
 
	//  析构函数
	~SmartPtr()
	{
    
    
		ptr_cnt->cnt--;
		if (ptr_cnt->cnt == 0)
			delete ptr_cnt;
		else
			cout << "还有" << ptr_cnt->cnt << "个指针指向基础对象" << endl;
	}
};

shared_ptr: Die Referenzzählermethode wird verwendet, um mehreren intelligenten Zeigern das Zeigen auf dasselbe Objekt zu ermöglichen. Immer wenn ein weiterer Zeiger auf das Objekt zeigt, werden die internen Referenzzähler aller intelligenten Zeiger, die auf das Objekt zeigen, um 1 erhöht, und jedes Mal, wenn a Der intelligente Zeiger wird so reduziert, dass er auf das Objekt zeigt. Der Referenzzähler wird um 1 verringert. Wenn der Zähler 0 ist, werden die dynamisch zugewiesenen Ressourcen automatisch freigegeben.
shared_ptr-Initialisierung:


std::shared_ptr<T> sp; //空shared_ptr,可以指向类型为T的对象
 
std::shared_ptr<int> sp(new int(5)); //指定类型,传入指针通过构造函数初始化
 
std::shared_ptr<int> sp = std::make_shared<int>(5); //使用make_shared函数初始化
 
//智能指针是一个模板类,不能将一个原始指针直接赋值给一个智能指针,因为一个是类,一个是指针
std::shared_ptr<int> sp = new int(1); //error

unique_ptr unique_ptr ist der eindeutige Eigentümer des Objekts, auf das es verweist, und es kann immer nur ein unique_ptr geben, der auf ein bestimmtes Objekt zeigt. Durch die Übertragung eines unique_ptr wird der gesamte Besitz vom Quellzeiger auf den Zielzeiger übertragen und der Quellzeiger wird geleert. Daher unterstützt unique_ptr keine gewöhnlichen Kopier- und Zuweisungsvorgänge und kann nicht in STL-Standardcontainern verwendet werden ; mit Ausnahme des Rückgabewerts von lokale Variablen (da der Compiler das Gerät weiß, dass das zurückzugebende Objekt zerstört wird); wenn Sie einen unique_ptr kopieren, verweisen die beiden unique_ptr nach Abschluss des Kopiervorgangs auf dieselbe Ressource, was zu mehreren Freigaben desselben Speichers führt Zeiger am Ende und führt zum Absturz des Programms.
Initialisierung: Es gibt keine make_shared-Funktion, es können nur Zeiger über new übergeben werden.

std::unique_ptr<T> up; //空unique_ptr,可以指向类型为T的对象,up会使用delete来释放它的指针
 
std::unique_ptr<int> up(new int(5)); //绑定动态对象

unique_ptr hat keinen Kopierkonstruktor und unterstützt keine gewöhnlichen Kopier- und Zuweisungsvorgänge; es bietet jedoch einen Bewegungsmechanismus, um den Besitz des Zeigers von einem unique_ptr auf einen anderen unique_ptr zu übertragen (mit der Funktion std::move können Sie auch release oder reset aufrufen). )

std::unique_ptr<int> upMove = std::move(up); //转移所有权

std::unique_ptr<int> up1(new int(5));
std::unique_ptr<int> up2(up1.release()); //up2被初始化为up1原来保存的指针,且up1置为空
std::unique_ptr<int> up3(new int(6));
up2.reset(up3.release()); //reset释放了up2原来指向的内存,指向up3原来保存的指针,且将up3置为空

Unique_ptr hat ein breites Anwendungsspektrum. Es kann den Besitz dynamischer Anwendungsressourcen innerhalb der Funktion zurückgeben, Zeiger im Container speichern und die Verwaltung dynamischer Arrays unterstützen.

schwach_ptr ist ein schwacher Referenzzeiger, der mit shared_ptr geliefert wird und nicht das Verhalten gewöhnlicher Zeiger aufweist. Es gibt keine überladenen *- und ->-Operatoren in der Vorlagenklasse, was bedeutet, dass Zeiger vom Typ schwach_ptr nur auf den Zeiger des Heap-Speichers zugreifen können in der Lage sein, es zu ändern. . Es löst hauptsächlich das Problem der Referenzzählung von shared_ptr: Es führt zu Speicherverlusten, wenn Zirkelverweise erstellt werden .
schwacher_ptr zeigt auf ein von shared_ptr verwaltetes Objekt. Durch das Binden eines schwachen_ptr an einen geteilten_ptr wird der Referenzzähler von shared_ptr nicht geändert. Wenn ein Teil des Speichers gleichzeitig von shared_ptr und schwach_ptr referenziert wird und alle gemeinsam genutzten_ptr zerstört werden, wird der Speicher freigegeben, unabhängig davon, ob noch schwach_ptr auf den Speicher verweist . Daher garantiert schwach_ptr nicht, dass der Speicher, auf den er verweist, gültig sein muss. Überprüfen Sie vor der Verwendung mit der Funktion lock(), ob schwach_ptr ein Nullzeiger ist.
use_count() Zeigt die Anzahl der shared_ptr-Zeiger an, die auf denselben Zeiger wie der aktuelle schwache_ptr-Zeiger zeigen.
expired() Stellen Sie fest, ob der aktuelle schwache_ptr-Zeiger abgelaufen ist (der Zeiger ist leer oder der gezeigte Heap-Speicher wurde freigegeben). lock()
Wenn der aktuelle schwache_ptr abgelaufen ist, gibt die Funktion einen leeren shared_ptr-Zeiger zurück; andernfalls wird die Funktion dies tun return a Es zeigt auf denselben shared_ptr-Zeiger wie der aktuelle schwache_ptr.
Zirkelverweis: A ruft B auf und B ruft A auf, sodass die Anzahl von A und B während der Initialisierung 1 plus 1 während der Zuweisung beträgt und bei der Zerstörung um 1 dekrementiert wird . Schließlich ist es immer noch 1 und die Ressource wird nicht freigegeben.
Fügen Sie hier eine Bildbeschreibung ein
Ändern Sie einfach eine beliebige Mitgliedsvariable von A oder B in schwach_ptr:

Geben Sie den Zeiger this nicht als shared_ptr zurück, da dieser Zeiger im Wesentlichen ein Rohzeiger ist. Daher kann die Rückgabe von this zu einer wiederholten Zerstörung führen:
Der richtige Weg, den shared_ptr davon zurückzugeben, ist: Lassen Sie die Zielklasse die Klasse std::enable_shared_from_this übergeben und verwenden Sie dann die Mitgliedsfunktion shared_from_this() der Basisklasse, um den shared_ptr zurückzugeben von diesem:

Sind intelligente Zeiger threadsicher?
Fazit: Derselbe shared_ptr kann sicher von mehreren Threads gelesen werden; derselbe shared_ptr kann nicht sicher von mehreren Threads geschrieben werden; es ist sicher, unterschiedliche shared_ptr mit gemeinsamen Referenzzählern von mehreren Threads zu „schreiben“.
Der Grund dafür ist, dass shared_ptr tatsächlich aus Zeigern auf Objekte und Zählern besteht. Zähleradditions- und -subtraktionsoperationen sind atomare Operationen, daher ist dieser Teil threadsicher, Zeiger auf Objekte sind jedoch nicht threadsicher. Beispielsweise kopiert die Zuweisungskopie eines intelligenten Zeigers zuerst den Zeiger auf das Objekt und addiert und subtrahiert dann die Anzahl der Referenzen. Obwohl das Addieren und Subtrahieren der Anzahl von Referenzen eine atomare Operation ist, handelt es sich beim Kopieren um eine zweistufige Operation Der Zeiger und die Anzahl der Referenzen sind keine atomaren Operationen und der Thread ist nicht sicher. Manuelles Sperren und Entsperren.

auto automatische Typableitung (in früheren Versionen wurde das Schlüsselwort auto verwendet, um den Speichertyp der Variablen anzugeben, der relativ zum Schlüsselwort static ist. a auto bedeutet, dass die Variable automatisch gespeichert wird , was auch die Standardregel des Compilers ist , also write Es ist dasselbe, wenn Sie es nicht schreiben, und im Allgemeinen schreiben wir es nicht, was die Existenz des Schlüsselworts auto sehr geschmacklos macht.)
Ein typisches Anwendungsszenario von auto besteht darin, den Iterator von stl zu definieren. Die Iteratoren verschiedener Container haben unterschiedliche Typen, die bei der Definition des Iterators angegeben werden müssen. Der Typ des Iterators ist manchmal kompliziert und umständlich zu schreiben; auto wird für die generische Programmierung verwendet, wenn Sie keinen bestimmten Typ angeben möchten, wie beispielsweise bei der generischen Programmierung.
Es gibt auch einen Decl-Typ, der auto sehr ähnlich ist, und es handelt sich ebenfalls um einen automatischen Typabzug.
decltype (exp) varname [= value] Die Klammern bedeuten, dass sie weggelassen werden können. Der Unterschied besteht darin, dass der Typ anhand des exp- Ausdrucks abgeleitet wird und der Ausdruck einfach oder eine Funktion sein kann. Weil auto initialisiert werden muss, decltype jedoch nicht. Auto kann nicht für nicht statische Mitgliedsvariablen einer Klasse verwendet werden (auch weil sie nicht initialisiert ist), decltype jedoch schon.

R-Wert-Referenz in C++11 ist nur eine neue C++-Syntax. Was wirklich schwer zu verstehen ist, sind die beiden C++-Programmiertechniken, die von R-Wert-Referenzen abgeleitet sind, nämlich Bewegungssemantik und perfekte Weiterleitung .
In C++ oder der C-Sprache wird ein Ausdruck (der ein Literal, eine Variable, ein Objekt, ein Rückgabewert einer Funktion usw. sein kann) entsprechend unterschiedlichen Verwendungsszenarien in L-Wert-Ausdrücke und R-Wert-Ausdrücke unterteilt. Genauer gesagt ist das Konzept von lvalue und rvalue in C++ von der C-Sprache geerbt.
1 Ein Ausdruck, der auf der linken Seite eines Zuweisungszeichens (=) stehen kann, ist ein L-Wert; umgekehrt ist ein Ausdruck, der nur auf der rechten Seite eines Zuweisungszeichens stehen kann, ein R-Wert. Zum Beispiel:
int a = 5; 5 = a; //Fehler, 5 kann kein L-Wert sein.
Ein L-Wert in C++ kann auch als R-Wert verwendet werden, zum Beispiel:
int b = 10; // b ist ein L-Wert
a = b; // a und b sind beide L-Werte, aber b kann als R-Wert verwendet werden

2 Ein Ausdruck, der einen Namen hat und die Speicheradresse erhalten kann, ist ein l-Wert; andernfalls ist er ein r-Wert. a und b sind Variablennamen, und ihre Speicheradressen können über &a und &b abgerufen werden, sodass a und b beide L-Werte sind. Im Gegensatz dazu haben die Literale 5 und 10 weder Namen noch ihre Speicheradressen (Literale werden normalerweise in einem Register gespeichert. oder mit dem Code gespeichert), also sind 5, 10 R-Werte.
Einfach ausgedrückt sind L-Werte Variablen und R-Werte Literalkonstanten.

Zitieren, verwenden Sie „&“, wie gesagt. Diese Art von Referenzmethode weist jedoch einen Fehler auf: Unter normalen Umständen können in C ++ nur L-Werte verarbeitet werden, und R-Werte können nicht referenziert werden. Zum Beispiel:
int num = 10;
int &b = num; //correct
int &c = 10; //error
Aus diesem Grund führt der C++11-Standard eine weitere Referenzmethode namens rvalue reference mit „ &&“ express ein.
int && a = 10;
a = 100;
cout << a << endl; (Aber diese Art der konstanten R-Wert-Referenz ist bedeutungslos, da sie auf eine nicht veränderbare Konstante verweist, da dies durch eine konstante L-Wert-Referenz erfolgen kann.) Sehr
quantitative R-Wert-Referenzen können erreicht werden Bewegungssemantik und perfekte Weiterleitung. Das durch
Bewegungssemantik
gelöste Problem: Wenn beim Kopieren eines Objekts das Objektmitglied einen Zeiger hat, handelt es sich um eine Deep-Copy-Methode (bei flachem Kopieren besteht das Problem der Mehrfachzerstörung), Deep Copy zeigt auf die Speicherressourcen bis der Zeiger zusammen kopiert wird. Wenn die Zeigermitglieder im temporären Objekt eine große Menge an Heap-Speicherplatz beantragen, ist die Effizienz sehr gering.
Die sogenannte Bewegungssemantik bezieht sich auf die Initialisierung von Klassenobjekten, die Zeigerelemente enthalten, durch Verschieben statt durch tiefes Kopieren. Einfach ausgedrückt bezieht sich die Verschiebungssemantik auf das „Verschieben zu genutzten“ Speicherressourcen, die anderen Objekten gehören (normalerweise temporären Objekten ). std::move verschiebt nichts, seine einzige Funktion besteht darin, einen L-Wert in eine R-Wert-Referenz umzuwandeln, und dann kann der Wert über eine R-Wert-Referenz für die Verschiebungssemantik verwendet werden. Kann auch mit unique_ptr verwendet werden, um den Besitz zu übertragen.

Implementierungsmethode: Manuell einen Konstruktor dafür hinzugefügt. Im Gegensatz zu anderen Konstruktoren verwendet dieser Konstruktor Parameter in Form von R-Wert-Referenzen , die auch als Verschiebungskonstruktoren bezeichnet werden. Und in diesem Konstruktor verwendet die Num-Zeigervariable eine flache Kopiermethode (Referenz ist eine flache Kopie) und setzt gleichzeitig den ursprünglichen Zeiger innerhalb der Funktion auf NULL zurück, wodurch effektiv vermieden wird, dass „derselbe Block mehrmals freigegeben wird“. Eintritt der Situation.
Wir wissen, dass nicht konstante R-Wert-Referenzen nur mit R-Werten arbeiten können und temporäre Objekte, die in Programmausführungsergebnissen generiert werden (z. B. Funktionsrückgabewerte, Lambda-Ausdrücke usw.), weder Namen noch Zugriff auf ihre Speicheradressen haben, also R-Werte sind . Wenn eine Klasse sowohl einen Kopierkonstruktor als auch einen Verschiebungskonstruktor enthält und ein temporäres Objekt zum Initialisieren eines Objekts der aktuellen Klasse verwendet wird, gibt der Compiler dem Aufruf des Verschiebungskonstruktors Priorität, um diesen Vorgang abzuschließen.

Das Konzept der perfekten Weiterleitung ist nur für die Vorlagenprogrammierung nützlich und wenn die Leistung von Funktionen sehr wichtig ist.
Zunächst müssen wir die Situation der Referenzfaltung verstehen :
Die Referenzfaltung ist ein Konzept in der Vorlagenprogrammierung, das die Situation doppelter Referenzen (wie unten gezeigt) nach der Vorlagenableitung lösen soll.
Angenommen, eine Vorlagenfunktion ist wie folgt definiert:
template
void PrintType(T&& param){ ... }
Wenn T vom Typ int && ist, wird param als Typ int & && abgeleitet, und die C++-Syntax lässt diese Art von Doppelreferenztyp nicht zu existieren (Sie können eine Doppelreferenzvariable selbst definieren, der Compiler wird einen Fehler auslösen), daher wurden die Regeln für die Referenzfaltung formuliert. Die spezifischen Regeln lauten wie folgt: Wenn im Parametertypabzug eine Doppelreferenz vorhanden ist
Bei der Vorlagenprogrammierung** wird die doppelte Referenz in eine Referenz** zusammengefasst, entweder eine L-Wert-Referenz oder eine R-Wert-Referenz. Die Faltregel lautet: Wenn eine der Referenzen eine L-Wert-Referenz ist, ist das Ergebnis eine L-Wert-Referenz. Andernfalls (das heißt, beide sind R-Wert-Referenzen) ist das Ergebnis eine R-Wert-Referenz.

Perfekte Weiterleitung: Perfekte Weiterleitung, was bedeutet, dass eine Funktionsvorlage ihre eigenen Parameter „perfekt“ an andere intern aufgerufene Funktionen weiterleiten kann . Die sogenannte Perfektion bedeutet, dass nicht nur der Wert des Parameters genau weitergeleitet werden kann, sondern auch sichergestellt wird, dass die linken und rechten Wertattribute des weitergeleiteten Parameters unverändert bleiben. Ob eine perfekte Weiterleitung in vielen Szenarien erreicht wird, bestimmt daher direkt, ob der Parameterübergabeprozess Kopiersemantik (Aufruf des Kopierkonstruktors) oder Verschiebungssemantik (Aufruf des Verschiebungskonstruktors) verwendet. Wie erreicht man eine
perfekte Weiterleitung?
Im Allgemeinen verwenden wir beim Definieren einer Vorlagenfunktion das grammatikalische Format einer R-Wert-Referenz, um den Parametertyp zu definieren , sodass die Funktion entweder einen von der Außenwelt übergebenen L-Wert oder einen R-Wert empfangen kann (andernfalls wird sie nur als R-Wert betrachtet). ein lvalue (aufgrund der Regeln der Referenzfaltung); zweitens ist es auch notwendig, die von der C++11-Standardbibliothek bereitgestellte forword()- aufgerufenen Funktion zu ändern, die links und rechts beibehalten müssen Wertattribute und die Weiterleitung wird einheitlich in Form eines L-Wert-Referenzparameterparam empfangen . Dadurch ist eine perfekte Weiterleitung von Parametern in Funktionsvorlagen einfach zu erreichen. (Der Vorteil ist ähnlich wie bei einer Zustandsmaschine, der Anfangszustand ist derselbe, der Zwischenprozess ist derselbe und das Endergebnis ist derselbe.)

Listeninitialisierung (Einheitlichkeit der Initialisierung)
In C++ 11 können Sie direkt nach dem Variablennamen eine Initialisierungsliste hinzufügen, um das Objekt zu initialisieren. Vor C++11 gab es hauptsächlich die folgenden Initialisierungsmethoden: Klammern, Gleichheitszeichen. Die Initialisierungsliste für den Konstruktor. So viele Objektinitialisierungsmethoden erhöhen nicht nur die Lernkosten, sondern verändern auch den Codestil erheblich, was sich auf die Lesbarkeit und Einheitlichkeit des Codes auswirkt.

28 std::function implementiert den Rückrufmechanismus, um zu verstehen,
was eine Rückruffunktion ist: Wenn Sie den Funktionszeiger (Adresse) als Parameter an eine andere Funktion übergeben und dieser Zeiger zum Aufrufen der Funktion verwendet wird, auf die er zeigt, sagen wir dies die Callback-Funktion. Die Rückruffunktion wird nicht direkt vom Implementierer der Funktion aufgerufen, sondern von einer anderen Partei, wenn ein bestimmtes Ereignis oder eine bestimmte Bedingung auftritt, und zur Reaktion auf das Ereignis oder die Bedingung verwendet.
Rückruf: Entkopplung führt zu: Effizient und flexibel Die Kapselung von modernem C++ führt zu einem gewissen Grad an Unabhängigkeit zwischen Modulen. Da die Module jedoch miteinander zusammenarbeiten müssen, ist
ein Rückruf intuitiver: Angenommen, A ist das Kernteam (A stellt B eine funA-Funktion zum Aufruf bereit, die als Bibliotheksfunktion betrachtet werden kann, und es gibt eine Verkaufsfunktion, die darauf warten muss, dass funA das Ergebnis zurückgibt. Ausführung), B ist ein anderes Team.
Wenn die Ausführung von funA lange dauert und kein Rückruf verwendet wird, muss A warten, bis B die Ausführung abgeschlossen hat, bevor er den nächsten sale() ausführt, was Zeitverschwendung ist.
Der Rückruf ist funA (sell). Obwohl die Verkaufsrückruffunktion von A definiert wird, wird sie von B aufgerufen. Nach dem Aufruf wird das Ergebnis direkt an A zurückgegeben, und A muss nicht warten. Das ist effizient
und flexibel: ** Wenn Sie die Verkaufsfunktion direkt in die funA-Funktion schreiben, ist alles gut. FunA weiß jedoch nicht, was später zu tun ist, nur der Aufrufer weiß es. Wenn es also von verschiedenen Leuten aufgerufen wird, müssen Sie nur unterschiedliche Funktionszeiger übergeben.

Denken: Warum es verwenden?
Aber die Anwendung von Funktionszeigern auf Callback-Funktionen verkörpert eine Strategie zur Lösung von Problemen und eine Idee zum Entwurf von Systemen. Bevor ich diese Idee erläutere, möchte ich erklären, dass die Rückruffunktion sicherlich einige Probleme der Systemarchitektur lösen kann, aber nicht überall im System vorhanden sein darf. Wenn Sie feststellen, dass Ihr System voller Rückruffunktionen ist, müssen Sie Ihr System umgestalten . Die Rückruffunktion selbst ist eine Entwurfsidee, die die Systemstruktur zerstört.
Das heißt, das Wesentliche der Rückruffunktion ist: „Nur wir B wissen, was zu tun ist, aber wir wissen nicht, wann wir es tun sollen, nur andere Module A.“ wissen, also müssen wir das, was wir wissen, in einer Rückruffunktion kapseln, um es anderen Modulen mitzuteilen.“

Ein Funktionszeiger kann eine gewöhnliche Funktion oder eine statische Funktion einer Klasse sein.
Es kann auch eine nicht statische Funktion einer Klasse sein.

Warum es verwenden?
Im Zeitalter der C-Sprache können wir Funktionszeiger verwenden, um eine Funktion als Parameter zu übergeben, sodass wir den Rückruffunktionsmechanismus implementieren können. Nach C++11 wurde die Vorlagenklasse std::function in der Standardbibliothek eingeführt. Diese Vorlage fasst das Konzept von Funktionszeigern zusammen.
std::function<int(int a, int b)> plusFunc;
sie kann jede Rückgabe darstellen Der Wert ist ein int, und die formale Parameterliste ist ein Funktionszeiger wie int a, int b.
int puls(int a, int b)

{ return a + b; } // Der Funktionsname stellt die Adresse der Funktion dar, also den Zeiger auf die Funktion plusFunc = plus; es hat die gleiche Form wie ein aufrufbares Objekt. Ein aufrufbares Objekt ist nicht nur ein Funktionszeiger, es kann auch ein Lambda-Ausdruck, ein Member-Funktionszeiger einer Klasse usw. sein. Wenn std::function mit der entsprechenden Parameterliste und dem entsprechenden Rückgabewert gefüllt ist, wird es zu einem Funktions-Wrapper, der alle aufrufenden Methoden aufnehmen kann. Wenn std::function kein Ziel enthält, gilt es als leer, und der Aufruf des Ziels einer leeren std::function führt zu einer std::bad_function_call-Ausnahme.




std::bind hat normalerweise zwei Funktionen:
Binden Sie das aufrufbare Objekt und die Parameter zusammen, während eine andere std::function zum Aufrufen (wie eine Funktion)
nur einige Parameter bindet und die vom aufrufbaren Objekt übergebenen Parameter reduziert . So binden Sie eine normale Funktion mit dem Platzhalter
std::bind
double callableFunc (double x, double y) {return x/y;}
auto NewCallable = std::bind (callableFunc, std::placeholders::_1,2);
std: :cout << NewCallable (10) << '\n';
Der erste Parameter von bind ist der Funktionsname. Wenn eine gewöhnliche Funktion als tatsächlicher Parameter verwendet wird, wird sie implizit in einen Funktionszeiger umgewandelt. Daher ist std::bind(callableFunc,_1,2) äquivalent zu std::bind (&callableFunc,_1,2);
_1 stellt einen Platzhalter dar, der sich in std::placeholders::_1 befindet;
der erste Parameter wird durch die Bitbelegung belegt bedeutet, dass dieser Parameter dem beim Aufruf übergebenen Parameter unterliegt. Wenn hier NewCallable aufgerufen wird, wird 10 übergeben, was tatsächlich dem Aufruf von callableFunc(10,2) entspricht;

Lambda-Ausdrücke sind kürzer, können Kontextdaten erfassen und Rückgabewertdefinitionen können weggelassen werden. nicht sehr nützlich,

Der nebenläufigkeitsbezogene
std::thread implementiert Multithreading.
Vor C++ 11 können Sie pthread_xxx zum Erstellen von Threads verwenden, was umständlich und unlesbar ist. C++ 11 führt std::thread zum Erstellen von Threads ein.
std::thread t(func); c++11 bietet auch Funktionen wie das Abrufen der Thread-ID oder der Anzahl der System-CPUs, das Abrufen des nativen_Handles des Threads und das Versetzen des Threads in den Ruhezustand; std::thread t(func); cout <<
"
aktuelle Thread-ID " << t.get_id() << endl;
cout << "aktuelle CPU-Nummer" << std::thread::hardware_concurrency() << endl;
auto handle = t.native_handle(); // handle Kann für pthread-bezogene Vorgänge verwendet werden
std::this_thread::sleep_for(std::chrono::seconds(1));

std::Mutex ist hauptsächlich in gegenseitigen Ausschluss mit und ohne Zeitüberschreitung und gegenseitigen Ausschluss ohne Rekursion unterteilt.
C++ 11 umfasst hauptsächlich std::lock_guard, RAII-Sperrkapselung, die Sperrressourcen dynamisch freigeben und verhindern kann, dass Threads aufgrund von Codierungsfehlern Sperren halten.

Eine Bedingungsvariable ist ein von C++11 eingeführter Synchronisierungsmechanismus. Sie kann einen oder mehrere Threads blockieren, bis eine Thread-Benachrichtigung oder ein Timeout den blockierten Thread aufweckt. Die Bedingungsvariable muss in Verbindung mit der Sperre verwendet werden. Die Sperre hier ist das obige Wir stellen std::unique_lock vor.

std::future ist fortschrittlicher als std::thread, std::future wird als Übertragungskanal für asynchrone Ergebnisse verwendet, der Rückgabewert der Thread-Funktion kann einfach über get () abgerufen werden, und std::promise wird verwendet Umschließen Sie einen Wert und binden Sie die Daten an die Zukunft. Mit std::packaged_task wird ein Aufrufobjekt umschlossen, die Funktion und die Zukunft gebunden und asynchrone Aufrufe erleichtert. Der std::future kann nicht kopiert werden. Wenn Sie ihn in den Container kopieren müssen, können Sie std::shared_future verwenden.

std::future Wir möchten das Ergebnis der asynchronen Aufgabe
vom Thread zurückgeben (dh die Ausführung von Aufgabe A muss vom Rückgabewert von Aufgabe B abhängen). Aus Sicherheitsgründen müssen wir uns im Allgemeinen auf globale Variablen verlassen Dies ist nicht angemessen. Aus diesem Grund stellt C+ +11 die Klassenvorlage std::future bereit, das zukünftige Objekt bietet einen Mechanismus für den Zugriff auf die Ergebnisse asynchroner Vorgänge und es ist einfach, das Problem der Rückgabe von Ergebnissen asynchroner Aufgaben zu lösen . Einfach ausgedrückt kann std::future verwendet werden, um die Ergebnisse asynchroner Aufgaben zu erhalten , sodass es als einfaches Mittel zur Synchronisierung zwischen Threads angesehen werden kann. std::future wird normalerweise von einem Provider erstellt. Sie können sich einen Provider als Anbieter einer asynchronen Aufgabe vorstellen. Der Provider legt den Wert des gemeinsam genutzten Status in einem Thread fest und das mit den gemeinsam genutzten Statusaufrufen verknüpfte std::future-Objekt get (Normalerweise in einem anderen Thread) Rufen Sie den Wert ab. Wenn das Flag des gemeinsam genutzten Status nicht bereit ist, blockiert der Aufruf von std::future::get den aktuellen Aufrufer, bis der Anbieter den Wert des gemeinsam genutzten Status festlegt (zu diesem Zeitpunkt). Shared State Das Flag wird bereit), std::future::get gibt den Wert der asynchronen Aufgabe oder einer Ausnahme zurück (wenn eine Ausnahme auftritt). Der Anbieter kann eine Funktion oder eine std::async-ähnliche Funktion sein, und der std: Die Funktion :async() wird später in diesem Artikel vorgestellt. std::promise::get_future, get_future ist eine Mitgliedsfunktion der Promise-Klasse std::packaged_task::get_future, derzeit ist get_future eine Mitgliedsfunktion von packet_task






std::future wird im Allgemeinen von std::async, std::promise::get_future, std::packaged_task::get_future erstellt, es werden jedoch auch Konstruktoren bereitgestellt. Der Kopierkonstruktor von td::future ist deaktiviert, nur der Standardkonstruktor und der Verschiebungskonstruktor (Verschiebekonstruktor, r-Wert als Parameter verwenden) std::future::valid() prüft den aktuellen
std
:: Ob das zukünftige Objekt gültig ist, d. h. Die Veröffentlichung ist mit einem gemeinsamen Status verknüpft. Ein gültiges std::future-Objekt kann nur über std::async(), std::future::get_future oder std::packaged_task::get_future initialisiert werden. Darüber hinaus ist das vom Standardkonstruktor von std::future erstellte std::future-Objekt ungültig (ungültig). Natürlich kann das std::future-Objekt auch nach der Verschiebungszuweisung von std::future gültig werden.
std::future::wait()
wartet darauf, dass das Flag des gemeinsam genutzten Status, der mit dem aktuellen std::future-Objekt verknüpft ist, bereit wird.
Wenn das Flag des gemeinsam genutzten Status nicht bereit ist (der Anbieter legt keinen Wert fest (bzw eine Ausnahme) für den gemeinsam genutzten Zustand zu diesem Zeitpunkt) ), blockiert der Aufruf dieser Funktion den aktuellen Thread, bis das Flag des gemeinsam genutzten Zustands bereit wird. Sobald das Flag des gemeinsam genutzten Zustands bereit wird, kehrt die Funktion wait() zurück und der aktuelle Thread wird entsperrt, aber wait() liest den Wert oder die Ausnahme des gemeinsam genutzten Zustands nicht. (Dies ist der Unterschied zu std::future::get())
std::future::wait_for() kann einen Zeitraum rel_time festlegen
std::shared_future ähnelt std::future, aber std::shared_future kann kopiert werden und mehrere std::shared_future können einen gemeinsamen Status teilen. Der endgültige Ergebnisparameter ist eine Zukunft
, und diese Zukunft wird verwendet, um auf eine int zu warten Typ Produkt: std::future& fut
Verwenden Sie die Methode get() im untergeordneten Thread, um auf eine zukünftige Zukunft zu warten und ein Ergebnis zurückzugeben. Der Produzent verwendet die asynchrone Methode, um Produktionsarbeiten durchzuführen und eine Zukunft zurückzugeben, und der Verbraucher verwendet die get()-Methode in der Zukunft, um das Produkt zu erhalten.

C++ 14, 17 wird nicht im Detail vorgestellt. C++20 verfügt über mehr Coroutinen.

auto automatische Typableitung (in früheren Versionen wurde das Schlüsselwort auto verwendet, um den Speichertyp der Variablen anzugeben, der relativ zum Schlüsselwort static ist. a auto bedeutet, dass die Variable automatisch gespeichert wird , was auch die Standardregel des Compilers ist , also write Es ist dasselbe, wenn Sie es nicht schreiben, und im Allgemeinen schreiben wir es nicht, was die Existenz des Schlüsselworts auto sehr geschmacklos macht.)
Ein typisches Anwendungsszenario von auto besteht darin, den Iterator von stl zu definieren. Die Iteratoren verschiedener Container haben unterschiedliche Typen, die bei der Definition des Iterators angegeben werden müssen. Der Typ des Iterators ist manchmal kompliziert und umständlich zu schreiben; auto wird für die generische Programmierung verwendet, wenn Sie keinen bestimmten Typ angeben möchten, wie beispielsweise bei der generischen Programmierung.
Es gibt auch einen Decl-Typ, der auto sehr ähnlich ist, und es handelt sich ebenfalls um einen automatischen Typabzug.
decltype (exp) varname [= value] Die Klammern bedeuten, dass sie weggelassen werden können. Der Unterschied besteht darin, dass der Typ anhand des exp- Ausdrucks abgeleitet wird und der Ausdruck einfach oder eine Funktion sein kann. Weil auto initialisiert werden muss, decltype jedoch nicht. Auto kann nicht für nicht statische Mitgliedsvariablen einer Klasse verwendet werden (auch weil sie nicht initialisiert ist), decltype jedoch schon.

Das letzte Schlüsselwort verhindert, dass die Klasse vererbt wird und die Funktion nicht neu geschrieben werden kann. Durch Überschreiben wird die virtuelle Funktion aufgefordert, den
Standardwert neu zu schreiben: Meistens wird es verwendet, um den Konstruktor als Standardkonstruktor zu deklarieren, wenn die Klasse einen benutzerdefinierten Konstruktor enthält , wird der Compiler Der Standardkonstruktor wird nicht implizit generiert; und manchmal möchten wir das Kopieren und Zuweisen von Objekten verbieten, wir können Löschänderungen verwenden , die Delele-Funktion wird in C++ 11 sehr häufig verwendet, std::unique_ptr ist es um das Kopieren von Objekten durch Löschung und Änderung zu verbieten.

explizit wird speziell zum Ändern des Konstruktors verwendet, was bedeutet, dass er nur explizit erstellt werden kann und nicht implizit konvertiert werden kann (für einen Parameter). const bedeutet
nur die Semantik des Nur-Lesens, was nur garantiert, dass es zur Laufzeit nicht geändert werden kann. aber es wird immer noch geändert Es kann sich um eine dynamische Variable handeln, und der geänderte constexpr ist die tatsächliche Konstante, die während der Kompilierung berechnet wird und während des gesamten laufenden Prozesses nicht geändert werden kann. constexpr kann zum Ändern von Funktionen und dem Rückgabewert von verwendet werden Diese Funktion wird wann immer möglich zur Kompilierungszeit als Konstante berechnet

Das größte Merkmal von Tupel- Tupel ist: Das instanziierte Objekt kann jede beliebige Datenmenge jedes Typs speichern.
C++11 Long Long Super Long Shaping
C++11 Shared_ptr, Unique_ptr, Weak_ptr Smart Pointer Einzelheiten finden Sie auf der Vorderseite

Es gibt auch einige neue Algorithmen, alle mit einem oder ohne

Geerbter Konstruktor: Da es viele Konstruktoren der Basisklasse gibt, muss die abgeleitete Klasse den Konstruktor der Basisklasse neu schreiben. Sie können ihn mit base::base verwenden, ohne dass ein Umschreiben erforderlich ist.

Supongo que te gusta

Origin blog.csdn.net/weixin_53344209/article/details/130488817
Recomendado
Clasificación