类型推导
类型推导是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不可以被复制,但是可以被移动。可以使用std::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;
}
参考:
C++11 中文
C++11 博客园
右值引用 博客园