构造函数与析构函数
构造函数 | 析构函数 | |
---|---|---|
调用 | 创建对象时自动调用 | 析构对象时自动调用 |
作用 | 初始化成员,创建堆对象等 | 释放资源 |
重载 | 支持 | 不支持 |
- 构造函数有多种形式,根据参数自动调用
- 默认构造函数
- 拷贝构造函数
- 转移构造函数(C++11)
- 创建对象时机有很多
- 函数输入 (拷贝构造函数)
- 函数返回 (拷贝构造函数)
- 对象构造对象 (拷贝构造函数)
- 临时对象构造对象 (转移构造函数)
- 普通变量构造对象 (默认构造函数)
condition variable
wait()
details
execution details(step 1,2,3 undiviable)
- unlock mutex
- block current thread
- add this thread to waiting-list(or wait to be notified)
once cv is notified, excute as below:
- lock mutex
- go on until exit
join
and detach
join
阻塞当前线程,直到子线程结束运行detach
分离线程,与当前线程没有关系
- 主线程如果为main,退出后,其他线程也会关闭
- 如果线程对象析构时未指定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;
}
- 首先创建线程执行is_prime(700020007),返回一个std::future对象。
- 主线程使用std::future::wait_for等待结果返回,wait_for可设置最长等待时间。
- 任务完成,返回std::future_status::ready
- 任务尚未完成,返回std::future_status::timeout
- 主线程既可使用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;
}
- Promise对象可保存T类型的值
- get_future来获取与promise对象关联的对象,调用该函数之后,两个对象共享相同的共享状态(shared state)。
- Promise对象某一时刻设置共享状态的值。
- 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); //未绑定变量或者未完全绑定变量
注:
- std中使用function统一了
- 普通函数
- 匿名函数(lambada)
- 含参函数(bind)
-
如果希望异步接受函数,可以通过future类(async,packaged_task,promise来获取异步的结果)
-
当调用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());
四种类型转换
- static_cast //静态转换,不会进行安全检查
- dynamic_cast //动态转换,会执行安全检查
- const_cast //将const转成非const
- reinpreter_cast //强制成任意类型
右值引用
- 可以给临时变量“续命”
int &&r1 = fun(); auto r2 = [] {return 5; };
- 变量转移构造函数,避免深度拷贝
A(A&& a) {this.data=a.data}
- 完美转发 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&的优点
- 支持动态修改绑定对象
- ref.get()返回的引用对象等同于T&
- 通过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;
}
侯捷培训回顾
少用define
更加类型安全,无需括号等
- const 取代 define 变量
- inline取代 define 函数
构造函数执行
- 基类构造函数
- 成员构造函数
- 自身构造函数
当没有指定构造函数时,隐式的构造函数如下所示:
D():Base(),Member(){};
当需要指定初始值时,就需要显示调用构造函数了
remark
- 推荐使用初始化表,避免某些编辑器二次赋值
- 执行顺序与初始化表无关,与定义顺序有关
类的两个指针
- this指针
调用成员函数时,由编辑器传入。(成员函数也是一种普通函数) - 虚表指针
同一类型的对象各自拥有一个指针,指向同一个表
new与delete刨析
new
- 申请空间
- 指针转换
- 构造函数
delete
- 析构函数
- 释放空间
overwrite与overload
overwrite只适用于虚函数 //这里的write指的是重写虚函数表
overload 适用于函数重载
modern C++ 两大关键改进
- 变参模板
- 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的缓存。
遇到以下两种应用场景:
- 当汇编与c代码混合编程时,编译器无法知道汇编代码中是否修改过变量,可能直接从cpu缓存中读取,导致错误。
- 断点调试并且通过外部命令行进行变量设置,如果不设置成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
除了常规用法外,个人觉得以下几点注意:
- 向下转换(父类转成子类) //这是不安全的
- 左右值转换
- 不同枚举类型的转换,以及与int之间的转换
- 去const属性用const_cast。
- 基本类型转换用static_cast。
- 多态类之间的类型转换用daynamic_cast。
- 不同类型的指针类型转换用reinterpret_cast。 //除了类型的向上转换用static
参考链接
RTTI 机制
运行时类型检查
- typeid函数: 如果有虚函数,执行动态检查,否则执行静态检查。
- dynamic_cast: 一般用于向下的类型转换,运行时会动态检查以及调整。 //占用运行时间,且有其他隐患
reference