1、initializer_list
auto i = {1, 2, 3, 4};
cout << typeid(i).name()<<endl;
//输出
//class std::initializer_list<int>
c++11中的vector<int> vec{1,2,3};就是使用了initializer_list,可变参数也是使用的initializer_list。initializer_list最常用的方式是通过大括号包围的值列表对其进行初始化:除了不能修改vlist中的值以外,可以像一般的list一样使用。
vector<int>和initializer_list的区别
- vector有push_back操作,其有reserve重新new内存空间,是在堆中进行的操作,而initializer_list是在栈上,其内部只有begin和end函数,函数内不能修改这些数值
- vector会发生拷贝,而initializer_list是指针语义,里面的元素不会被拷贝
2、map
- map没有赋值的时候是被初始化为0的
std::map<int, int> m; int a = m[1110]; //输出 a为0
- map自定义键值类型
map底层的实现是红黑树,红黑树具有二叉搜索树的性质,map在插入的时候具有对key进行排序的功能,这是因为c++的基本类型有默认的比较方式,即便是string类型,其可以自动排序那是因为string内部实现了operator < 的重载。
类或结构体中重载operator<
#include <iostream> #include <map> #include <string> using namespace std; class Person { public: string name; int age; Person(string n, int a) { name = n; age = a; } //重载operator< bool operator<(const Person &p) const //注意这里的两个const { return (age < p.age) || (age == p.age && name.length() < p.name.length()); } }; int main() { map<Person, int> group; group[Person("David", 27)] = 2; group[Person("Eric", 18)] = 3; for (auto iter = group.begin(); iter != group.end(); iter++) { cout << iter->first.name << " " << iter->first.age << " : " << iter->second << endl; } return 0; }
3、为什么析构函数是虚函数
当基类的指针指向派生类的时候要将基类的析构函数设置为虚函数,这样是为了防止派生类无法析构导致内存泄露
- 基类析构未使用虚函数
#include<iostream> using namespace std; class Base { public: Base() {}; ~Base() { cout << "Output from the destructor of class Base!" << endl; }; void DoSomething() { cout << "Do something in class Base!" << endl; }; }; class Derived : public Base { public: Derived() {}; ~Derived() { cout << "Output from the destructor of class Derived!" << endl; }; void DoSomething() { cout << "Do something in class Derived!" << endl; } }; int main() { Base *p = new Derived; p->DoSomething(); delete p; return 0; }
上述代码,未使用virtual,输出的结果没有进入派生类的析构函数中,会导致内存泄露的发生。
- 基类析构函数使用虚函数
#include<iostream> using namespace std; class Base { public: Base() {}; virtual ~Base() { cout << "Output from the destructor of class Base!" << endl; }; void DoSomething() { cout << "Do something in class Base!" << endl; }; }; class Derived : public Base { public: Derived() {}; ~Derived() { cout << "Output from the destructor of class Derived!" << endl; }; void DoSomething() { cout << "Do something in class Derived!" << endl; } }; int main() { Base *p = new Derived; p->DoSomething(); delete p; return 0; }
根据多态的性质,在释放的时候就是先析构派生类,然后在析构基类
4、static介绍
- 静态资源是类初始化的时候加载的,
- 而非静态资源是类实例化对象的时候加载的,
- 类的初始化早于类实例化对象
- 静态成员函数属于整个类,在类实例化之前就已经分配空间了。而非静态成员必须在类实例化之后才能有内存空间。
#include <iostream> using namespace std; class Point { public: void init() { } static void output() { //error 报非静态成员引用必须与特定对象对应,即静态成员函数不能调用非静态变量 cout << m_x << endl; } static void output1() { //ok 没有非静态成员就可以 cout << “hello”<< endl; } private: int m_x; }; void main() { Point pt; pt.output(); }
- 非静态成员函数可以调用静态变量,前提是静态变量要提前在类外初始化
#include <stdio.h> class Point { public: Point() { m_nPointCount++; } ~Point() { m_nPointCount--; } static void output() { printf("%d\n", m_nPointCount); } private: static int m_nPointCount; int m_1; }; int Point::m_nPointCount = 0; //初始化则ok,否则不ok void main() { Point pt; pt.output(); }
5、私有构造函数
- 将构造函数私有化之后就不能在类的外部构造对象了,也不能在外部构造子类的对象了。但是可以通过一个public的static静态函数来访问类中定义的函数和静态成员,单例模式就是这样。
- 这样的好处就是可以阻止用户在类外调用析构该对象了。
#include<iostream> using namespace std; class Singleton { private: Singleton() { cout << "Singleton" << endl; } public: static Singleton* getInstance() { if (m_instance == nullptr) m_instance = new Singleton; return m_instance; } //析构函数中释放,delete是不对的,析构函数中的delete会再次进入析构,导致循环调用了 ~Singleton() { //if (m_instance) //{ // cout << "~Singleton" << endl; // delete m_instance; // m_instance = nullptr; //} } //释放 void Release() { if (m_instance) { cout << "~Singleton" << endl; delete m_instance; m_instance = nullptr; } } private: static Singleton *m_instance; }; Singleton *Singleton::m_instance = nullptr; int main() { Singleton* m1 = Singleton::getInstance(); Singleton* m2 = Singleton::getInstance(); m1->Release(); //error //delete m1; //m1 = nullptr; }
也可以通过在类内部定义一个静态成员变量来释放单例的内存,静态成员变量会在程序结束的时候自动进入析构释放资源。 所以该静态成员变量是需要是一个实现析构的类。
程序在结束
的时候,系统会自动析构所有的全局变量和所有的类的静态成员变量
。利用这个特征,我们可以在单例类中定义一个静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例
。#include<iostream> using namespace std; class Singleton { private: Singleton() { cout << "Singleton" << endl; } public: static Singleton* getInstance() { if (m_instance == nullptr) m_instance = new Singleton; return m_instance; } ~Singleton() { //if (m_instance) //{ // cout << "~Singleton" << endl; // delete m_instance; // m_instance = nullptr; //} } void Release() { if (m_instance) { cout << "~Singleton" << endl; delete m_instance; m_instance = nullptr; } } /* 程序在结束的时候,系统会自动析构所有的全局变量和所有的类的静态成员变量。 利用这个特征,我们可以在单例类中定义一个静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。 */ class CRelease { public: ~CRelease() { if (m_instance) { cout << "~Singleton" << endl; delete m_instance; m_instance = nullptr; } } private: static CRelease m_re; }; private: static Singleton *m_instance; }; Singleton *Singleton::m_instance = nullptr; Singleton::CRelease mRelease; //静态变量需要初始化,要不然进不去析构函数 int main() { Singleton* m1 = Singleton::getInstance(); Singleton* m2 = Singleton::getInstance(); //m1->Release(); }
shared_ptr并自定义deleter
#include <iostream>
#include <mutex>
using namespace std;
mutex mtx;
class Singleton
{
public:
static shared_ptr<Singleton> getInstance()
{
//双重检查,避免单重检查每次都加锁
if (m_instance == nullptr)
{
lock_guard<mutex> lck(mtx);
if(m_instance == nullptr)
m_instance = make_shared<Singleton>();
}
return m_instance;
}
private:
static void Release(Singleton *s)
{
cout << "Release" << endl;
delete s;
}
private:
static shared_ptr<Singleton> m_instance;
};
shared_ptr<Singleton> Singleton::m_instance(nullptr, Singleton::Release);
int main()
{
shared_ptr<Singleton> m1 = Singleton::getInstance();
shared_ptr<Singleton> m2 = Singleton::getInstance();
}
6、thread的拷贝、转移问题
c++11中的thread不能被拷贝,其内部没有拷贝构造函数,thread的源码如下:
thread(thread&& _Other) noexcept : _Thr(_Other._Thr) { // move from _Other _Thr_set_null(_Other._Thr); } thread& operator=(thread&& _Other) noexcept { // move from _Other return (_Move_thread(_Other)); } thread(const thread&) = delete; thread& operator=(const thread&) = delete;
将thread传递的时候要使用右值引用和move函数
#include <thread> #include <iostream> using namespace std; class CThread { public: CThread(thread &th):m_th(move(th)) { cout << "\nCThread, id:" << m_th.get_id() << \ ", joinable status is:" << m_th.joinable() << endl; } ~CThread() { cout << "~CThread" << endl; m_th.join(); } CThread(const CThread &th) = delete; CThread &operator=(const CThread &th) = delete; private: thread m_th; }; void func() { cout << "func" << endl; } int main() { thread t(func); cout << "thread before move, id:" <<t.get_id()<< \ ", joinable status is:"<< t.joinable()<<endl; CThread th(t); cout << "\nthread after move, id:" << t.get_id() << \ ", joinable status is:" << t.joinable() << endl; }
7、lambda表达式作为函数参数
- 模板参数
编译器根据传参类型自动推断出T的类型,然后运行
#include <iostream> template <typename T> void print(const T &func) { std::cout << typeid(func).name() << std::endl; std::cout << "print " << std::endl; func(); } int main() { int val = 10; auto lambda_func = [&]() { std::cout << "lambda : " << val << std::endl; }; print(lambda_func); }
- function函数
#include <iostream> #include <functional> void output(std::function<void(int)> &func) { std::cout << "output " << std::endl; func(20); } int main() { int val = 10; //必须写成显式的std::function<void(int)>,而不能使用auto std::function<void(int)> func = [&](int x) { std::cout << "x : " << x <<", val: "<<val<< std::endl; }; output(func); }
- lambda作为线程函数
#include <iostream> #include <thread> int main() { int val = 10; auto func = [&](int x) { std::cout << "x : " << x <<", val: "<<val<< std::endl; }; std::thread t(func, 200); if (t.joinable()) t.join(); }
8、thread和async的比较
async用来创建异步任务,相比于thread其有三种不同的创建策略,并且其通过future来保存返回结果
template<class _Fty, class... _ArgTypes> _NODISCARD inline future<_Invoke_result_t<decay_t<_Fty>, decay_t<_ArgTypes>...>> async(launch _Policy, _Fty&& _Fnarg, _ArgTypes&&... _Args) 参数介绍 _Policy: 1.std::launch::async 异步启动,在调用std::async()时创建一个新的线程以异步调用函数,并返回 future对象; 2.std::launch::deffered 延迟启动,在调用std::async()时不创建线程,直到调用了future对象的get()或wait()方法时,才创建线程; 3.std::launch::async||std::launch::deffered 默认策略,由系统决定怎么调用 _Fnarg:函数指针(函数指针,函数对象,lambda表达式) _Args:函数参数列表
#include <future> #include <iostream> int main() { auto lam_func = [](int x) { std::this_thread::sleep_for(std::chrono::microseconds(500)); std::cout << "val = " << x << std::endl; }; std::future<void> f1 = std::async(std::launch::async, lam_func, 10); }
std::launch::deffered的时候在调用wait的时候才开始执行线程操作
#include <future> #include <iostream> int main() { int sum = 10; auto lam_func = [&](int x) { std::this_thread::sleep_for(std::chrono::microseconds(500)); sum += x; std::cout << "val = " << x <<", sum = "<<sum<< std::endl; return sum; }; std::future<int> f1 = std::async(std::launch::deferred, lam_func, 10); f1.wait(); auto ret = f1.get(); std::cout << "ret = " << ret << std::endl; }
future类成员函数
- get:当与该 std::future 对象相关联的共享状态标志变为 ready 后,调用该函数将返回保存在共享状态中的值,如果共享状态的标志不为 ready,则调用该函数会阻塞当前的调用者
- wait:等待与当前std::future 对象相关联的共享状态的标志变为 ready,如果共享状态的标志不是 ready,调用该函数会被阻塞当前线程,直到共享状态的标志变为 ready一旦共享状态的标志变为 ready,wait() 函数返回,当前线程被解除阻塞
- wait_for:和wait函数类似,需要设置一个时间段rel_time,如果共享状态的标志在时间段结束之前没有被Provider设置为valid,则调用wait_for的线程被堵塞,在等待了rel_time时间后,wait_for函数返回
- wait_until:和wait函数类似,需要设置一个系统绝对时间点abs_time,如果共享状态的标志在该时间点到来之前没有被 Provider 设置为 ready,则调用 wait_until 的线程被阻塞,在 abs_time 这一时刻到来之后 wait_until() 返回
wait_for以及wait_until返回值类型
- future_status::ready:共享状态的标志已经变为 ready,即 Provider 在共享状态上设置了值或者异常
- future_status::timemout:超时,即在规定的时间内共享状态的标志没有变为 ready
- future_status::deferred:共享状态包含一个 deferred 函数。如在saync中第一个参数指定为std::launch::deferred
#include <future> #include <iostream> int main() { int sum = 10; auto lam_func = [&](int x) { std::this_thread::sleep_for(std::chrono::microseconds(50)); sum += x; std::cout << "val = " << x <<", sum = "<<sum<< std::endl; return sum; }; std::future<int> f1 = std::async(std::launch::async, lam_func, 10); if (f1.wait_for(std::chrono::microseconds(0)) == std::future_status::ready) { std::cout << "ready" << std::endl; } else if (f1.wait_for(std::chrono::microseconds(0)) == std::future_status::timeout) { std::cout << "timeout" << std::endl; } else { std::cout << "defer" << std::endl; f1.get(); } }
thread的函数形式和async一样。重点说一下async和thread的区别
- std::thread:而thread是必定创建线程。当然系统资源紧张也会可能创建失败
- std:: async:主要是用于创建一个异步任务,当系统资源紧张时有可能不创建线程或者创建线程失败。并且当枚举宏为std::launch::defered时,肯定不会创建线程并且只有调用get或者wait才开始执行异步任务
- std::async函数模板更加容易拿到线程函数的返回值,所以更想要拿返回值的,可以使用std::async
- async不是线程池,但是其内部可以重复使用开辟的线程,而thread不行
#include <atomic> #include <vector> #include <future> #include <thread> #include <unordered_set> #include <iostream> int main() { std::atomic<int> someCount = 0; const int THREADS = 2000; std::vector<std::thread> threadVec(THREADS); std::vector<std::future<void>> futureVec(THREADS); std::unordered_set<std::thread::id> uniqueThreadIdsAsync; std::unordered_set<std::thread::id> uniqueThreadsIdsThreads; std::mutex mutex; auto lam = [&](bool isAsync) { for (int i = 0; i < 1000000; ++i) someCount++; auto threadId = std::this_thread::get_id(); if (isAsync) { std::lock_guard<std::mutex> lg(mutex); uniqueThreadIdsAsync.insert(threadId); } else { std::lock_guard<std::mutex> lg(mutex); uniqueThreadsIdsThreads.insert(threadId); } }; for (int i = 0; i < THREADS; ++i) threadVec[i] = std::thread(lam, false); for (int i = 0; i < THREADS; ++i) threadVec[i].join(); std::cout << "Number of threads used running std::threads = " << uniqueThreadsIdsThreads.size() << std::endl; for (int i = 0; i < THREADS; ++i) futureVec[i] = std::async(lam, true); for (int i = 0; i < THREADS; ++i) futureVec[i].get(); std::cout << "Number of threads used to run std::async = " << uniqueThreadIdsAsync.size() << std::endl; }