C++11

类型推导

类型推导是C++的一种特性,允许编译器自动推导变量的类型,而不需要显式地制定类型。

auto

auto用于让编译器自动推导变量类型,常见用法:

  1. 基本示例:
auto x = 10;
  1. 与容器一起使用:
vector<string> names = { "Alice", "Bob"};
for (auto it = names.begin(); it != names.end(); it++) {
    cout << *it;
}
  1. 与函数返回类型一起使用:
auto add(int a, int b) {
    return a+b;
}
  1. 与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的常见用法:

  1. 动态内存管理:unique_ptr可以实现自动管理动态分配的内存,防止内存泄漏。
unique_ptr<int> uptr(new int(5));//the int object will automatically deleted when uptr is out of scope
  1. 实现独占所有权语义: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));
}
  1. Pimpl惯用法:Pimpl惯用法是一种隐藏类的实现细节的技术,通常使用unique_ptr来实现。
class MyClass {
public:
    MyClass();
    ~MyClass();
    //other public member functions
private:
    class Impl;
    unique_ptr<Impl> pimpl;
};
  1. 自定义删除器: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 博客园
右值引用 博客园

posted on 2024-08-24 18:08  zc32  阅读(8)  评论(0编辑  收藏  举报

导航