C++ 查漏补缺

构造函数与析构函数

构造函数 析构函数
调用 创建对象时自动调用 析构对象时自动调用
作用 初始化成员,创建堆对象等 释放资源
重载 支持 不支持
  1. 构造函数有多种形式,根据参数自动调用
    • 默认构造函数
    • 拷贝构造函数
    • 转移构造函数(C++11)
  2. 创建对象时机有很多
    • 函数输入 (拷贝构造函数)
    • 函数返回 (拷贝构造函数)
    • 对象构造对象 (拷贝构造函数)
    • 临时对象构造对象 (转移构造函数)
    • 普通变量构造对象 (默认构造函数)

condition variable

wait() details

execution details(step 1,2,3 undiviable)

  1. unlock mutex
  2. block current thread
  3. add this thread to waiting-list(or wait to be notified)

once cv is notified, excute as below:

  1. lock mutex
  2. go on until exit

join and detach

  • join 阻塞当前线程,直到子线程结束运行
  • detach 分离线程,与当前线程没有关系
  1. 主线程如果为main,退出后,其他线程也会关闭
  2. 如果线程对象析构时未指定detach或者join,将会导致程序crash.

future

async

#include <future>
#include <iostream>

bool is_prime(int x)
{
  for (int i=0; i<x; i++)
  {
    if (x - i == 0)
      return false;
  }
  return true;
}

int main()
{
  std::future<bool> fut = std::async(is_prime, 700020007);
  std::cout << "please wait";
  std::chrono::milliseconds span(100);
  while (fut.wait_for(span) != std::future_status::ready)
    std::cout << ".";
  std::cout << std::endl;

  bool ret = fut.get();
  std::cout << "final result: " << ret << std::endl;
  return 0;
}
  1. 首先创建线程执行is_prime(700020007),返回一个std::future对象。
  2. 主线程使用std::future::wait_for等待结果返回,wait_for可设置最长等待时间。
    • 任务完成,返回std::future_status::ready
    • 任务尚未完成,返回std::future_status::timeout
  3. 主线程既可使用std::future::get获取结果,且主线程阻塞至任务完成。

promise

#include <iostream>       // std::cout
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

void print_int(std::future<int>& fut) {
    std::cout << "start wait";
    int x = fut.get(); // 获取共享状态的值.
    std::cout << "value: " << x << '\n'; // 打印 value: 10.
}

int main()
{
    std::promise<int> prom; // 生成一个 std::promise<int> 对象.
    std::future<int> fut = prom.get_future(); // 和 future 关联.
    std::thread t(print_int, std::ref(fut)); // 将 future 交给另外一个线程t.
    std::this_thread::sleep_for(std::chrono::seconds(5));
    prom.set_value(10); // 设置共享状态的值, 此处和线程t保持同步.
    t.join();
    return 0;
}
  1. Promise对象可保存T类型的值
  2. get_future来获取与promise对象关联的对象,调用该函数之后,两个对象共享相同的共享状态(shared state)。
  3. Promise对象某一时刻设置共享状态的值。
  4. Future对象可以返回共享状态的值(阻塞)

packaged_task

相比与async, 能够跟更加精细化控制线程的执行。

三者对比

相同点:
获取异步线程中返回数据
不同点:

  • async层次最高,你只需要给它提供一个函数,它就会返回一个future对象。接下来就只需等待结果了。
  • packaged_task次之,你在创建了packaged_task后,还要创建一个thread,并把packaged_task交给它执行。
  • promise就最低,在创建了thread之后,你还要把对应的promise作为参数传入。这还没完,别忘了在函数中手动设置promise的值。
#include <chrono>
#include <future>
#include <iostream>
#include <thread>

int main()
{
    std::packaged_task<int()> task([](){ 
            std::chrono::milliseconds dura( 2000  );
            std::this_thread::sleep_for( dura  );
            return 0; 
            });
    std::future<int> f1 = task.get_future();
    std::thread(std::move(task)).detach();

    std::future<int> f2 = std::async(std::launch::async, [](){ 
            std::chrono::milliseconds dura( 2000  );
            std::this_thread::sleep_for( dura  );
            return 0; 
            });

    std::promise<int> p;
    std::future<int> f3 = p.get_future();
    std::thread([](std::promise<int> p){ 
            std::chrono::milliseconds dura( 2000  );
            std::this_thread::sleep_for( dura  );
            p.set_value(0); 
            },
            std::move(p)).detach();

    std::cout << "Waiting..." << std::flush;
    f1.wait();
    f2.wait();
    f3.wait();
    std::cout << "Done!\nResults are: "
        << f1.get() << " " << f2.get() << " " << f3.get() << "\n";
    return 0;
}

线程池

C++ 线程池的实现

#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>

class ThreadPool {
public:
    ThreadPool(size_t);    //构造函数,size_t n 表示连接数

    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args)   //任务管道函数
        -> std::future<typename std::result_of<F(Args...)>::type>;  //利用尾置限定符  std future用来获取异步任务的结果

    ~ThreadPool();
private:
    // need to keep track of threads so we can join them
    std::vector< std::thread > workers;   //追踪线程
                                          // the task queue
    std::queue< std::function<void()> > tasks;    //任务队列,用于存放没有处理的任务。提供缓冲机制

                                                  // synchronization  同步?
    std::mutex queue_mutex;   //互斥锁
    std::condition_variable condition;   //条件变量?
    bool stop;
};

// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads) : stop(false)
{
    for (size_t i = 0; i<threads; ++i)
        workers.emplace_back(     //以下为构造一个任务,即构造一个线程
            [this]
    {
        for (;;)
        {
            std::function<void()> task;   //线程中的函数对象
            {//大括号作用:临时变量的生存期,即控制lock的时间
                std::unique_lock<std::mutex> lock(this->queue_mutex);
                this->condition.wait(lock,
                    [this] { return this->stop || !this->tasks.empty(); }); //当stop==false&&tasks.empty(),该线程被阻塞 !this->stop&&this->tasks.empty()
                if (this->stop && this->tasks.empty())
                    return;
                task = std::move(this->tasks.front());
                this->tasks.pop();

            }

            task(); //调用函数,运行函数
        }
    }
    );
}

// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)  //&& 引用限定符,参数的右值引用,  此处表示参数传入一个函数
-> std::future<typename std::result_of<F(Args...)>::type>
{
    using return_type = typename std::result_of<F(Args...)>::type;
    //packaged_task是对任务的一个抽象,我们可以给其传递一个函数来完成其构造。之后将任务投递给任何线程去完成,通过
    //packaged_task.get_future()方法获取的future来获取任务完成后的产出值
    auto task = std::make_shared<std::packaged_task<return_type()> >(  //指向F函数的智能指针
        std::bind(std::forward<F>(f), std::forward<Args>(args)...)  //传递函数进行构造
        );
    //future为期望,get_future获取任务完成后的产出值
    std::future<return_type> res = task->get_future();   //获取future对象,如果task的状态不为ready,会阻塞当前调用者
    {
        std::unique_lock<std::mutex> lock(queue_mutex);  //保持互斥性,避免多个线程同时运行一个任务

                                                         // don't allow enqueueing after stopping the pool
        if (stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");

        tasks.emplace([task]() { (*task)(); });  //将task投递给线程去完成,vector尾部压入
    }
    condition.notify_one();  //选择一个wait状态的线程进行唤醒,并使他获得对象上的锁来完成任务(即其他线程无法访问对象)
    return res;
}//notify_one不能保证获得锁的线程真正需要锁,并且因此可能产生死锁

 // the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();  //通知所有wait状态的线程竞争对象的控制权,唤醒所有线程执行
    for (std::thread &worker : workers)
        worker.join(); //因为线程都开始竞争了,所以一定会执行完,join可等待线程执行完
}


int main()
{

    ThreadPool pool(4);
    std::vector< std::future<int> > results;

    for (int i = 0; i < 8; ++i) {
        results.emplace_back(
            pool.enqueue([i] {
            std::cout << "hello " << i << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
            std::cout << "world " << i << std::endl;
            return i*i;
        })
        );
    }

    for (auto && result : results)    //通过future.get()获取返回值
        std::cout << result.get() << ' ';
    std::cout << std::endl;

    return 0;
}

std::function和std::bind

bind函数使用:
auto f4 = std::bind(fun_2, n,placeholders::_1);
返回对象:std::function
输入1:函数名
输入其他:

  • 使用时才输入值
    • place_holder: 传递 //根据参数列表确定值传递还是引用传递
  • 与当前对象绑定 //想省略掉部分或者全部的参数值
    • 某个变量:值传递 //即使参数列表是引用传递
    • std::ref(变量):引用传递 //需要参数列表保持一致
      调用
      f4(); //完全绑定
      f4(1,m); //未绑定变量或者未完全绑定变量

注:

  1. std中使用function统一了
  • 普通函数
  • 匿名函数(lambada)
  • 含参函数(bind)
  1. 如果希望异步接受函数,可以通过future类(async,packaged_task,promise来获取异步的结果)

  2. 当调用bind函数时,值已经传入,或者认为形参已经生成,即使后期改变传入变量值,bind函数只会按照传入时的值执行。如下所示的这段代码会导致计数器+1

    LOG("%d", weak_from_this().use_count());
    auto x2 = std::bind(&Connection::asyncRead, shared_from_this());
    LOG("%d", weak_from_this().use_count());

参考链接

四种类型转换

  1. static_cast //静态转换,不会进行安全检查
  2. dynamic_cast //动态转换,会执行安全检查
  3. const_cast //将const转成非const
  4. reinpreter_cast //强制成任意类型

四种转换参考

static与dynamic区别

右值引用

  1. 可以给临时变量“续命”
    int &&r1 = fun();
    auto r2 = [] {return 5; };
    
  2. 变量转移构造函数,避免深度拷贝
    A(A&& a) {this.data=a.data}
    
  3. 完美转发 std::forward
    void processValue(int& a) { cout << "lvalue" << endl; }
    void processValue(int&& a) { cout << "rvalue" << endl; }
    template <typename T>
    void forwardValue(T&& val)
    {
        //这里切记,&&只是告诉编译器只接受右值,可以认为是一种接口约定,但进入函数后,这就是左值引用啦,也可以对他赋值。!!!正因为如此才需要完美转发
        //processValue(val); //无论左右值,都会按照左值进行转发
        processValue(std::forward<T>(val)); //照参数本来的类型进行转发
    }
    void Testdelcl()
    {
        int i = 0;
        forwardValue(i); //传入左值 
        forwardValue(0);//传入右值 
    }
    输出:
        lvaue
        rvalue
    

reference_wrapper 与 ref

对应于C中的& C++表达形式(int& c=a)
std::reference_wrapper<int> c = std::ref(a);

refernce_wrapper相比于T&的优点

  1. 支持动态修改绑定对象
  2. ref.get()返回的引用对象等同于T&
  3. 通过bind机制在值传递函数中实现引用传递

变长参数函数

经典C语言中通过va_list args等去实现
C++中有三种选择
其中初始化列表是运行时,作为一个对象传入,其他都是编译期间自动推导的

初始化列表

initializer_list

递归展开

需要有两个函数

  • 展开函数
  • 终止函数
#include <iostream>
using namespace std;
//递归终止函数
void print()
{
   cout << "empty" << endl;
}
//展开函数
template <class T, class ...Args>
void print(T head, Args... rest)
{
   cout << "parameter " << head << endl;
   print(rest...);
}

int main(void)
{
   print(1,2,3,4);
   return 0;
}

逗号表达式

编译期间,
{(printarg(args), 0)…}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc… )

template <class T>
void printarg(T t)
{
   cout << t << endl;
}

template <class ...Args>
void expand(Args... args)
{
   int arr[] = {(printarg(args), 0)...};
}

expand(1,2,3,4);

模板元编程

将数值作为模板的参数,在编译期间就完成相关的计算
例如求固定值的幂

template<int NUM>
class T
{
public:
    //enum { v = num * T<num - 1>::v };  also ok
    static const int value = NUM*T<NUM - 1>::value;
};

template<>
class T<1>
{
public:
    static const int value = 1;
};

int main() {
    std::cout << T<5>::value;
    return 0;
}

宏定义与可变参数

#include <iostream>

//base
#define fun_1(str) {std::cout<<str<<std::endl;}

//example of "#"
#define fun_2(function) \
{   \
    if (!function)\
    {\
        std::cout << "[Error] in" << #function << std::endl;\
    }\
}

//example of "##"
#define fun_append(str1, str2, str3)   "HASH:"##str1##str2##str3

//example of "VA_ARGS"
#define LOG(...)     fprintf(stdout, __VA_ARGS__)

//mix example of virable agruments fuction and macro
template<typename T>  //must be ahead of template<typename T1, typename ...Rest>
void print(T t)
{
    std::cout << t << std::endl;
}

template<typename T1, typename ...Rest>
void print(T1 t1, Rest ...rest)
{
    std::cout << t1 << std::endl;
    print(rest...);  //rest... is a special virable,"rest" is nothing and cannot be used.
}

#define LOG2(...) print(__VA_ARGS__)


int checkNegtive(int val)
{
    return val < 0;
}

int main()
{
    fun_1("A");
    fun_2(checkNegtive(10));
    std::cout << fun_append("John", "23", "Male") << std::endl;
    LOG("ww_%d\n", 39);
    LOG2("love", 4, "you");
    return 0;
}

宏定义

可变参数模板MSDN

侯捷培训回顾

少用define

更加类型安全,无需括号等

  • const 取代 define 变量
  • inline取代 define 函数

构造函数执行

  1. 基类构造函数
  2. 成员构造函数
  3. 自身构造函数

当没有指定构造函数时,隐式的构造函数如下所示:
D():Base(),Member(){};
当需要指定初始值时,就需要显示调用构造函数了

remark

  • 推荐使用初始化表,避免某些编辑器二次赋值
  • 执行顺序与初始化表无关,与定义顺序有关

类的两个指针

  1. this指针
    调用成员函数时,由编辑器传入。(成员函数也是一种普通函数)
  2. 虚表指针
    同一类型的对象各自拥有一个指针,指向同一个表

new与delete刨析

new

  1. 申请空间
  2. 指针转换
  3. 构造函数

delete

  1. 析构函数
  2. 释放空间

overwrite与overload

overwrite只适用于虚函数 //这里的write指的是重写虚函数表
overload 适用于函数重载

modern C++ 两大关键改进

  1. 变参模板
  2. std::move操作

其他

  • big3(构造,析构,拷贝)在非指针对象中不需要自己写
  • 函数模板可以自行推导,类模板不可以
  • 同一类型的对象,彼此互为friend

pragma once

这只能防止在一个cpp文件中重复包含一个.h文件,而不是所有的cpp只包含一次,因为每个cpp都是独立编译的。因此,不要在头文件中定义变量。

initializer_list

通过大括号,生成一个initializer_list对象,将此作为一个大对象传入。

//原始写法
std::vector<int> vec;
vec.push_back(1);
vec.push_back(3);
vec.push_back(3);
vec.push_back(2);
//现代写法
std::vector<int> aa({1,2,3,4});
std::vector<int> aa={1,2,3,4};

//现代写法的其他场景
std::map<string, string> const nameToBirthday = {
       {"lisi", "18841011"},
       {"zhangsan", "18850123"},
       {"wangwu", "18870908"},
       {"zhaoliu", "18810316"},
   };

除了写法变成简洁以外,还可以借此实现可变参数函数

参考链接

volatile

背景:编译器优化过程中,会检查变量是否修改过,如果没有修改,可能直接从cpu缓存等缓存中读取,而不是内存。
方法:通过设置volatile,避免编译器的优化,强制从内存中获取变量,而不是CPU的缓存。
遇到以下两种应用场景:

  1. 当汇编与c代码混合编程时,编译器无法知道汇编代码中是否修改过变量,可能直接从cpu缓存中读取,导致错误。
  2. 断点调试并且通过外部命令行进行变量设置,如果不设置成volatile,就可能导致变量外部设置失败。

enum

推荐使用enum class 代替enum,否则以下操作就会通过编译,进而带来安全隐患。

	enum  Color { black, white, red };
	enum  Size {big, small };
	Color cl=Color::black;
	Size sz=Size::big;
	if (sz == cl) //bad code
	if (1==sz)  //bad code

static_cast

除了常规用法外,个人觉得以下几点注意:

  1. 向下转换(父类转成子类) //这是不安全的
  2. 左右值转换
  3. 不同枚举类型的转换,以及与int之间的转换

static_cast官方链接

  • 去const属性用const_cast。
  • 基本类型转换用static_cast。
  • 多态类之间的类型转换用daynamic_cast。
  • 不同类型的指针类型转换用reinterpret_cast。 //除了类型的向上转换用static
    参考链接

RTTI 机制

运行时类型检查

  1. typeid函数: 如果有虚函数,执行动态检查,否则执行静态检查。
  2. dynamic_cast: 一般用于向下的类型转换,运行时会动态检查以及调整。 //占用运行时间,且有其他隐患
    reference

猜你喜欢

转载自blog.csdn.net/lugaoyong/article/details/87890162