C++11
類型推導
類型推導是C++的一種特性,允許編譯器自動推導變量的類型,而不需要顯式地制定類型。
auto
auto用于讓編譯器自動推導變量類型,常見用法:
- 基本示例:
auto x = 10;
- 與容器一起使用:
vector<string> names = { "Alice", "Bob"};
for (auto it = names.begin(); it != names.end(); it++) {
cout << *it;
}
- 與函數返回類型一起使用:
auto add(int a, int b) {
return a+b;
}
- 與for循環一起使用:
for (const auto & name : names) {}
decltype
decltype關鍵字用于獲取表達式類型。通常用于模版編程和復雜類型推導:
int a = 10;
decltype(a) b = 11;
拖尾返回類型
拖尾返回類型是一種在函數聲明中指定函數返回類型的方式,通常與auto關鍵字結合使用。它在函數參數列表之后使用‘->’引出返回類型。
auto add(int a, int b) -> int {
return a+b;
}
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {
return x+y;
}
lambda
在C++中,lambda表達式是一種匿名函數,可以在代碼中定義并立即使用。它們非常適合用于短小的回調函數或臨時函數對象。
基本語法:
[capture list] (parameter list) option -> return type { function body}
capture list:捕獲子句,定義lambda表達式可以訪問的外部變量。
[] :不捕獲外部變量
[=]:按值捕獲所有外部變量
[&]:按引用捕獲所有外部變量
[a]:按值捕獲變量a
[&a]:按引用捕獲變量a
[a, &]:按值捕獲變量a,按引用捕獲其他變量
parameter list:參數列表,與普通函數相同
option:是函數選項;可以填 mutable,exception,attribute。(選填)
mutable說明lambda表達式體內的代碼可以修改被捕獲的變量,并且可以訪問被捕獲的對象的non-const方法。
exception說明lambda表達式是否拋出異常以及何種異常。
attribute用來聲明屬性。
return type:返回類型,通常情況下,編譯器可以自動推斷出lambda表達式的返回類型,因此不需要顯式的指出,但是,在一些復雜的情況下,例如條件語句,編譯器可能無法確定返回類型,此時需要顯式的指定返回類型。
lambda表達式中的捕獲變量是在lambda定義時進行的,也就是說,當lambda被定義時,對于每個lambda捕獲的變量,都會在lambda內部創建一個同名的克隆變量,這些克隆變量在此時就從同名的外部變量中初始化。
使用引用捕獲可能會導致一些潛在的問題,主要有以下兩點:
引用懸掛:如果捕獲的引用的生命周期比lambda表達式的生命周期要短,那么當lambda表達式被調用時,引用可能已經失效,這將導致未定義的行為。
std::function<void()> createLambda() { int x = 5; return [&x]() { cout << x; }; }數據競爭:如果多個線程同時訪問和修改同一個變量,而沒有適當的同步機制,就會發生數據競爭。如果在lambda中通過引用捕獲這個變量,并在多線程中使用這個lambda,就可能發生這種情況。
int x = 5; auto lambda = [&x]() { ++x; }; thread t1(lambda); thread t2{lambda); t1.join(); t2.join();
智能指針
C++11 引入三種智能指針:unique_ptr<T>,shared_ptr<T>,weak_ptr<T>。這些智能指針的主要目的是自動管理動態分配的內存,以防止內存泄漏。
unique_ptr<T>
unique_ptr<T>是一種獨占所有權的智能指針,當其離開作用域時會自動釋放所擁有的對象。
{
unique_ptr<int> uptr = make_unique<int>(200);
//離開uptr的作用域時會自動釋放內存
}
unique_ptr可以賦值給其他unique_ptr,但是這個過程實際上是轉移所有權,而不是復制所有權。這意味著一旦一個unique_ptr被賦值給另外一個unique_ptr,原來的unique_ptr將失去所有權,變為空指針。
如果想要將一個unique_ptr賦值給一個原始指針,可以使用get()成員函數獲取unique_ptr的原始指針。但是需要注意,這并不會改變unique_ptr的所有權,當unique_ptr被銷毀時,它仍然后釋放其所擁有的對象,即使有其他原始指針正指向它。
unique_ptr的常見用法:
- 動態內存管理:unique_ptr可以實現自動管理動態分配的內存,防止內存泄漏。
unique_ptr<int> uptr(new int(5));//the int object will automatically deleted when uptr is out of scope
- 實現獨占所有權語義:unique_ptr可以用于實現需要獨占所有權的數據結構或者算法。
class NoSharedData {
unique_ptr<int> data;
public:
NoSharedData(int value) : data(new int(value)) {}
//copy constructor and copy assignment operator are implicitly deleted
}
3.工廠函數:如果一個函數要返回一個新創建的對象,那么可以返回一個unique_ptr,以明確表示這個對象的所有權已經轉移給調用者。
unique_ptr<int> createInt(int value) {
return unique_ptr<int>(new int(value));
}
- Pimpl慣用法:Pimpl慣用法是一種隱藏類的實現細節的技術,通常使用unique_ptr來實現。
class MyClass {
public:
MyClass();
~MyClass();
//other public member functions
private:
class Impl;
unique_ptr<Impl> pimpl;
};
- 自定義刪除器:unique_ptr可以使用自定刪除器來處理特殊的資源清理需求,例如,管理打開的文件或鎖,當unique_ptr銷毀時,文件被關閉或鎖被釋放。
unique_ptr<FILE, decltype(&fclose)> fp(fopen("test.txt", "r"), &fclose);
注意:unique_ptr不可以被復制,但是可以被移動??梢允褂胹td::move來轉移一個unique_ptr所擁有的對象所有權。
shared_ptr<T>
shared_ptr是一種共享所有權的智能指針,它使用引用計數來跟蹤有過少個shared_ptr共享同一個對象。當最后一個shared_ptr被銷毀時,它會自動釋放其所指向的對象。
shared_ptr引用計數是原子操作,可以保證線程安全,但是shared_ptr指向的對象并不保證線程安全。更多使用建議CSDN
weak_ptr<T>
weak_ptr是一種智能指針,它持有對由shared_ptr管理的對象的非擁有(“弱”)引用。它必須轉換為shared_ptr才能訪問引用的對象(lock())。weak_ptr模擬臨時所有權:當一個對象只需要在存在時被訪問,并且它可能在任何時候被刪除。
weak_ptr可以解決懸掛指針問題。使用原始指針,我們無法知道數據是否已經被釋放。相反,通過讓shared_ptr管理數據,并向數據的用戶提供weak_ptr,用戶可以通過調用expired()和lock()來檢查數據的有效性。
int main()
{
shared_ptr<int> sptr;
//接管指針
sptr.reset(new int);
*sptr = 10;
weak_ptr<int> w1 = sptr;
//刪除管理對象,獲取新指針
sptr.reset(new int);
*sptr = 20;
weak_ptr<int> w2 = sptr;
//w1 已經過期
if ( auto tmp = w1.lock() ) {
cout << "w1 value is " << *tmp;
}
if (auto. tmp = w2.lock() ) {
cout << "w2 value is " << *tmp;
}
}
weak_ptr還可以解決shared_ptr相互引用產生死鎖的問題。
class B;
class A {
public:
void setB(shared_ptr<B> b) { b_ptr = b; }
void show() {
if (auto b = b_ptr.lock()) {
cout << "A has a reference to B";
} else {
cout << "B is destroyed";
}
}
~A() { cout << "~A"; }
private:
weak_ptr<B> b_ptr;
};
class B {
public:
void setA(shared_ptr<A> a) { a_ptr = a; }
void show() {
if (auto a = a_ptr.lock()) {
cout << "B has a reference to A";
} else {
cout << "A is destroyed";
}
}
~B() { cout << "~B"; }
private:
weak_ptr<A> A_ptr;
};
int main()
{
auto a = make_shared<A>();
auto b = make_shared<B>();
a->setB(b);
b->setA(a);
a->show();
b->show();
return 0;
}
右值引用
右值引用是C++11引入的新特性,用于實現移動語義和完美轉發。右值引用允許我們區分左值和右值,從而優化資源管理和性能。
左值:表示一個有名字的、可持久存在的對象;
右值:表示一個臨時對象或將要被銷毀的對象;
右值引用使用 && 語法,可以綁定到右值,從而實現移動語義。
一、右值引用的作用
- 實現移動語義
在 C++ 中,拷貝構造函數和賦值運算符通常用于對象的復制操作。但對于一些包含大量資源(如動態分配的內存、文件句柄等)的對象,復制操作可能非常耗時和消耗資源。
右值引用允許實現移動構造函數和移動賦值運算符,這些函數可以將資源從一個即將被銷毀的對象(右值)轉移到新對象中,而不是進行傳統的復制操作,從而提高性能。
例如,對于一個自定義的字符串類,如果沒有移動語義,當進行字符串賦值操作時,可能會進行大量的內存復制。而使用右值引用實現移動賦值運算符后,可以直接將源字符串的內存指針轉移到目標字符串,避免不必要的復制。
class ResourceManager {
private:
int* data;
public:
ResourceManager() : data(new int[1000]) { }
~ResourceManager() { delete[] data; }
ResourceManager(const ResourceManager& other) : data(new int[1000]) {
std::copy(other.data, other.data + 1000, data);
}
ResourceManager(ResourceManager&& other) noexcept : data(other.data) {
other.data = nullptr;
}
ResourceManager& operator=(ResourceManager&& other) noexcept {
if (this!= &other) {
delete[] data;
data = other.data;
other.data = nullptr;
}
return *this;
}
};
- 完美轉發
完美轉發是指函數模板能夠將自己的參數 “完美” 地轉發給另一個函數,保持參數的左值 / 右值屬性不變。
通過右值引用和模板參數推導,可以實現完美轉發。這在泛型編程中非常有用,例如在函數模板中,可以根據參數的實際類型決定是進行復制還是移動操作。
#include <iostream>
#include <utility>
template<typename T>
void func(T&& arg) {
anotherFunc(std::forward<T>(arg));
}
void anotherFunc(int& x) {
std::cout << "Called with lvalue reference." << std::endl;
}
void anotherFunc(int&& x) {
std::cout << "Called with rvalue reference." << std::endl;
}
int main() {
int a = 10;
func(a); // 傳入左值,調用 anotherFunc 的左值引用版本
func(20); // 傳入右值,調用 anotherFunc 的右值引用版本
return 0;
}
二、右值的分類
- 純右值
包括字面量、臨時對象等。例如,int i = 42;中的42是純右值,std::string s1 = "hello"; std::string s2 = s1 + " world";中的s1 + " world"也是純右值,它是一個臨時的字符串對象。- 將亡值
即將被銷毀的對象,可以通過右值引用捕獲并延長其生命周期。例如,std::vectorstd::string v1{"a", "b", "c"}; std::vectorstd::string v2 = std::move(v1);這里的v1在被std::move轉換后成為將亡值,可以被右值引用捕獲,觸發移動構造函數,將v1的資源轉移到v2。
三、使用右值引用的注意事項
- 右值引用只能綁定到右值,不能綁定到左值。但通過std::move可以將左值轉換為右值引用。
- 移動操作可能會使被移動的對象處于未定義狀態,除非對象明確支持多次移動。在使用移動后的對象時,需要注意其狀態可能已經改變。
- 右值引用和左值引用可以重載函數,但在調用時需要根據參數的類型來確定調用哪個版本。
多線程
Thread
C++11 引入了std::thread提供線程的創建和管理的函數或類的接口。
#include <iostream>
#include <thread>
void printNumber(int num) {
std::cout << "Number: " << num << std::endl;
}
int main() {
int number = 42;
std::thread t(printNumber, number);
t.join();
return 0;
}
mutex
C++11 新增<mutex>支持互斥鎖,保護資源共享。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutex1;
int counter = 0;
void incrementCounter() {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> guard(mutex1);
counter++;
}
}
int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl;
return 0;
}
condition_variable
C++11新增<condition_variable>提供條件變量功能,支持多線程之間同步。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mutex2;
std::condition_variable cv;
bool ready = false;
void waitingThread() {
std::unique_lock<std::mutex> lock(mutex2);
while (!ready) {
cv.wait(lock);
}
std::cout << "Waiting thread notified." << std::endl;
}
void notificationThread() {
std::this_thread::sleep_for(std::chrono::seconds(2));
{
std::lock_guard<std::mutex> lock(mutex2);
ready = true;
}
cv.notify_one();
std::cout << "Notification thread sent notification." << std::endl;
}
int main() {
std::thread t1(waitingThread);
std::thread t2(notificationThread);
t1.join();
t2.join();
return 0;
}
atomic
c++11新增<atomic>支持原子操作。std::atomic可以用于各種基本數據類型,如int、bool、double等。
#include <iostream>
#include <atomic>
#include <thread>
std::atomic<int> counter(0);
std::atomic<bool> flag(false);
std::atomic<double> value(0.0);
void incrementCounter() {
for (int i = 0; i < 1000; ++i) {
counter++;
}
}
int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);
t1.join();
t2.join();
std::cout << "Counter value: " << counter << std::endl;
return 0;
}
原子操作的方法:(cppreference)
- load():以原子方式讀取當前值。
- store():以原子方式存儲一個新值。
- exchange():以原子方式將當前值替換為新值,并返回舊值。
- compare_exchange_weak()和compare_exchange_strong():比較并交換操作,如果當前值等于預期值,則將其替換為新值。
std::atomic<int> num(10);
int oldValue = num.load();
num.store(20);
int newValue = num.exchange(30);
std::cout << "Old value: " << oldValue << ", New value: " << newValue << std::endl;
bool success = num.compare_exchange_strong(oldValue, 40);
std::cout << "Success: " << success << ", Current value: " << num << std::endl;
future
在 C++ 中,<future>頭文件提供了用于異步操作的工具。它允許你啟動一個異步任務,并在將來的某個時間獲取其結果。
std::promise:可以用來設置一個值或異常,供與之關聯的std::future對象獲取。
#include <iostream>
#include <future>
void setValue(std::promise<int>& p) {
p.set_value(42);
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t(setValue, std::ref(prom));
int value = fut.get();
std::cout << "Value: " << value << std::endl;
t.join();
return 0;
}
std::future:用于獲取異步操作的結果??梢酝ㄟ^get()方法阻塞等待結果,也可以使用wait()、wait_for()和wait_until()方法進行非阻塞等待。
異步函數(std::async)
std::async函數啟動一個異步任務,并返回一個std::future對象,用于獲取任務的結果。
#include <iostream>
#include <future>
int add(int a, int b) {
return a + b;
}
int main() {
std::future<int> fut = std::async(add, 10, 20);
int result = fut.get();
std::cout << "Result: " << result << std::endl;
return 0;
}
如果異步任務拋出異常,這個異??梢酝ㄟ^std::future的get()方法傳播到調用者線程。
#include <iostream>
#include <future>
void throwException() {
throw std::runtime_error("An error occurred.");
}
int main() {
std::future<void> fut = std::async(throwException);
try {
fut.get();
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
浙公網安備 33010602011771號