C++ 11/14/17

Posted by Bryan on July 20, 2018

C++ 11//14/17关键字

using关键字

  1. 通常我们使用 typedef 定义别名的语法是:typedef 原名称 新名称;,但是对函数指针等别名的定义语法却不相同。
    typedef int (*process)(void*);
    using process = int(*)(void *);
    using TrueDarkMagic = MagicType<std::vector<T>, std::string>;
    TrueDarkMagic<bool> you;
    
  2. 在传统 C++ 中,构造函数如果需要继承是需要将参数一一传递的,这将导致效率低下。C++11 利用关键字 using 引入了继承构造函数的概念:
    class BASE{
         public:
                 int value1;
                 int value2;
                 BASE(){
                         value1 = 1;
                 }
                 BASE(int value): BASE(){ //委托BASE()构造函数
                         value2 = 2;
                 }
    };
    class subBASE: public BASE{
         using BASE:BASE; //继承构造
    };
    int main(){
         subBASE s(3);
         std::cout << s.value1<<s.value2<<std::endl;
    }
    

C++11/14/17标准库

std::array

std::array保存在栈内存中,std::vector保存在堆内存.
std::array会在编译时创建一个固定大小的数组,std::array不能够被隐式的转换成指针.
使用std::array很简单,只需指定其类型和大小即可.

std::array<int, 4> arr= {1,2,3,4};
int len = 4;
std::array<int, len> arr = {1,2,3,4}; // *非法*, 数组大小参数必须是常量表达式

与C风格接口传参

void foo(int *p, int len) {
    return;
}
std::array<int, 4> arr = {1,2,3,4}; //实例

// C 风格接口传参
// foo(arr, arr.size()); // *非法*, 无法隐式转换
foo(&arr[0], arr.size());
foo(arr.data(), arr.size()); //arr.data() return 指向arr的指针
// 使用 `std::sort`
std::sort(arr.begin(), arr.end());

智能指针

智能指针实质是一个对象,行为表现的却像一个指针

引用计数不是垃圾回收,引用技术能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待,更能够清晰明确的表明资源的生命周期。

这些智能指针包括 std::shared_ptr/std::unique_ptr/std::weak_ptr,使用它们需要包含头文件

std::shared_ptr 是一种智能指针,它能够记录多少个 shared_ptr 共同指向一个对象,从而无需显示得调用 delete,当引用计数变为零的时候就会将对象自动删除。

  • shared_ptr and unique_ptr
    单个unique_ptr离开作用域时,会立即释放底层内存,既然是独占,换句话说就是不可复制;可以有多个shared_ptr实例指向同一块动态分配的内存,当最后一个shared_ptr离开作用域时,才会释放这块内存.

C++中正则表达式

正则表达式描述了一种字符串匹配的模式。一般使用正则表达式主要是实现下面三个需求:

  1. 检查一个串是否包含某种形式的子串;
  2. 将匹配的子串替换;
  3. 从某个串中取出符合条件的子串。

特殊字符 描述
$ 匹配输入字符串的结尾位置
(,) 标记一个子表达式的开始和结束位置
* 匹配前面的子表达式零次或多次
+ 匹配前面的子表达式一次或多次
. 匹配除换行符\n 之外的任何单字符

线程与并发

  1. 最简单的是使用std::thread创建一个线程实例,这是并发编程的基础,需要包含**头文件,其提供很多基本的线程操作:
    *get_id()* required ID of the created thead.
    *join()*  add one thread to pool.
    
    #include <iostream>
    #include <thread>
    void foo()
    {
     std::cout << "hello world" << std::endl;
    }
    int main()
    {
     std::thread t(foo); //new one thread
     t.join(); //start the thread, the main funtion waits until the thread ends
     return 0;
    }
    

    I wanna say that if we miss join a joinable thread, then the process will terminate because of destructure of the thread. The compiler doesn’t know join or detach the thread. The simple way to solving the problem is to adopt RAII. For example,

    class ThreadRAII{
     std::thread &m_thread;
     ThreadRAII(std::thread &th):m_thread(th){}
     ~ThreadRAII(){
         if(m_thread.isjoinable())
             m_thread.detach();
     }
    };
    void hello(){}
    int main(){
     std::thread t(hello);
     // if we comment the following line, there should be an exception terminating
     ThreadRAII(t);
     return 0;
    }
    
  2. std::mutex is the basic class in C++11, mutex variables can be created via instancing std::mutex. ** needs to be included. **member funtion**: lock() unlock() **object**: std::unique_lock<std::mutex> std::look_guard<std::mutex>
    #include <iostream>
    #include <thread>
    #include <mutex>
    std::mutex mtx;
    void block_area() {
    std::unique_lock<std::mutex> lock(mtx);
     //...临界区
    lock.unlock();
     //...some other code
    lock.lock(); //  can lock again
    }
    int main() {
     std::thread thd1(block_area);
     std::thread thd2(block_area);
     // 阻塞式,main等待线程接受后一并返回
     // 类似于while(true){},所以join的位置也很重要
     thd1.join(); 
     // 放在后台执行,与main无关了
     // 即使thread object已经回收了,该线程仍继续执行
     // 如果thread.joinable()为真,一定要detach或者join该线程
     // 检测线程是否joinable可以放在析构函数中
     thd2.detach(); 
     return 0;
    }
    
  3. std::future, std::packaged_task
    std::future提供了一个访问异步操作结果的途径,是一种简单的线程同步手段。
    std::packaged_task用来封装任何可以调用的目标,从而实现异步调用。
    #include <iostream> 
    #include <future> 
    #include <thread>
    #include <chrono>
    int main() {
    // 将一个返回值为int 7的lambda函数封装到packaged_task中
    // std::packaged_task 的模板参数为要封装函数的类型
    std::packaged_task<int()> task([]() {
     std::this_thread::sleep_for(std::chrono::seconds(3));
     return 7; 
    });
    // 获得packaged_task的 future
    std::future<int> result = task.get_future(); 
    // 在一个线程中执行packaged_task
    // move task and add to thread after task call 'get_future'
    // because move will change task
    std::thread th(std::move(task));
    //std::thread(std::move(task)).detach(); 
    std::cout << "Waiting...\n";
    // wait until packaged_task returns
    result.wait();
    std::cout << "Done!" << std::endl << "Result is " << result.get() << '\n';
    th.join();
    return 0;
    }
    
  4. std::condition_variable
    线程可能需要等待某个条件为真才能继续执行,而一个忙等待循环中可能会导致所有其他线程都无法进入临界区使得条件为真时,就会发生死锁。condition_variable实例被创建出现主要就是用于唤醒等待线程从而避免死锁。
  5. std::thread vs std::async
    You can see thread as cannot return value except using reference value and async (task are created with std::async) can return a value.
    int sum(int a, int b){
     return a + b;
    }
    std::future<int> ret = std::async(sum, 5, 6);
    std::cout << ret.get() << std::endl;
    

    You can pass about three types to them: function pointer (function name), functor (class with operator() overload), lambda function. In face, three of them are functions.

    class Accu{
    public:
     int sum_;
     void operator(int a, int b){
         sum_ = a + b;
     }
    };
    auto a1 = new Accu;
    std::thread(std::ref(*a1), 1, 2);
    // print 3 with ref, print random without ref
    std::cout << a1->sum_ << std::endl;
    

    If you don’t need to use member variable later, std::ref can be ignored. And you can use functor object. If you use member variable, like the following example, you should use a reference to functor object. And std::ref is similar to \& in lambda function. std::ref return an object of std::reference_wrapper which can be implicitly convertible to T&. std::reference_wrapper is used to pass objects by reference to std::bind, the constructor of std::thread, or the helper functions std::make_pair and std::make_tuple. For instance,

    int sum = 0;
    std::thread([&sum](){fuc(sum)});
    std::thread(fuc(std::ref(sum)));
    int hello(int&, int, int&){}
    //c is rvalue
    int res = std::bind(hello, std::ref(a), b, _1)(c); 
    

    Why is std::ref used in thread? That is:

    // one of std::thread ctors
    template< class Function, class... Args >
    explicit thread( Function&& f, Args&&... args );
    

    The above ctor creates new std::thread object and associates it with a thread of execution. The new thread of execution starts executing:

    std::invoke(decay_copy(std::forward<Function>(f)),
             decay_copy(std::forward<Args>(args))...);
    //decay_copy is defined as the following
    //template <class T>
    //std::decay_t<T> decay_copy(T&& v) { return std::forward<T>(v); }
    

    That means the variable f or args in ctor of thread can not be passed by reference unless using std::ref.

参数包

模板参数包是接受零或更多模板实参(非类型、类型或模板)的模板形参。函数模板形参报是接受零或更多函数实参的函数形参。
参考的链接:泛化之美
至少有一个参数包的模板被称作变参数模板
下面这种情况是类模板(变参数类模板可用任意数量的模板参数实例化):

template<class ... Types> struct Tuple {};
Tuple<> t0;           // Types 不包含实参
Tuple<int> t1;        // Types 包含一个实参: int
Tuple<int, float> t2; // Types 包含二个实参: int 与 float
Tuple<0> error;       // 错误: 0 不是类型

下面这种情况是函数模板(变参数函数模板可用任意数量的函数实参调用{模板参数通过模板实参推导推导}):

template<class ... Types> void f(Types ... args);
f();       // OK : args 不包含实参
f(1);      // OK : args 包含一个实参: int
f(2, 1.0); // OK : args 包含二个实参: int 与 double

在初等类模板中,模板参数包必须是模板形参列表的最后一个形参。所以这是错误的template<typename... Ts, typename U> struct Invalid;Ts... 应该放在结尾.
在函数模板中,模板参数包可以在列表中早于所有能从函数实参推导的参数出现,或拥有默认参数。所以:

template<typename ...Ts, typename U, typename=void>
void valid(U, Ts...);     // OK :能推导 U
// void valid(Ts..., U);  // 不能使用: Ts... 在此位置是非推导语境
valid(1.0, 1, 2, 3);      // OK :推导 U 为 double , Ts 为 {int,int,int}

右值引用

为了解决:第一个问题就是临时对象非必要的昂贵的拷贝操作,第二个问题是在模板函数中如何按照参数的实际类型进行转发。
区分左值和右值:能不能对表达式取地址,如果能,则为左值,否则为右值
非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等都是纯右值

int i = getVar(); // i是左值
T&& k = getVar(); // getVar()产生的临时值不会像第一行代码那样,在表达式结束之后就销毁了,而是会被“续命”。
                  // 他的生命周期将会通过右值引用得以延续,和变量k的声明周期一样长。
int g_constructCount=0;
struct{
  A(){
        cout<<"construct: "<<++g_constructCount<<endl;    
     }
};
A GetA()
{
    return A();
}
int main(){
  A a = GetA(); //需要生成临时对象,而后销毁临时对象
  const A& a = GetA(); // 与下面这句一样,少了一次拷贝和析构
  A&& a = GetA(); //少了一次拷贝和析构

通过右值引用,比之前少了一次拷贝构造和一次析构,原因在于右值引用绑定了右值,让临时右值的生命周期延长了。我们可以利用这个特点做一些性能优化,即避免临时对象的拷贝构造和析构.
常量左值引用是一个“万能”的引用类型,可以接受左值、右值、常量左值和常量右值。需要注意的是普通的左值引用不能接受右值,比如这样的写法是不对的:A& a = GetA();。但是右值引用独立于左值和右值,意思是右值引用类型的变量可能是左值也可能是右值,所以T&& t发生自动类型推断的时候(一般都是模板的T&&),它是未定的引用类型(universal references),如果被一个左值初始化,它就是一个左值;如果它被一个右值初始化,它就是一个右值,它是左值还是右值取决于它的初始化。

变量1 = 函数()–>返回值:这时出现函数返回一个临时变量,拷贝给变量1,一般此时要求返回值、变量的复制构造函数完成深拷贝,否则可能发生指针悬挂的问题,但是可以设置复制构造函数为A(A&& a) :m_ptr(a.m_ptr),这时只完成浅拷贝(移动构造,实际上std::move实际上它并不能移动任何东西,它唯一的功能是将一个左值强制转换为一个右值引用),即函数返回的右值直接付给了变量1,由于右值没有内存地址,所以不会发生指针悬挂的问题;以前的方式可以采用这样A(const A& a):m_ptr(new int(*a.m_ptr))完成深拷贝复制构造。

完美转发

C++11引入了完美转发:在函数模板中,完全依照模板的参数的类型(即保持参数的左值、右值特征),将参数传递给函数模板中调用的另外一个函数。C++11中的std::forward正是做这个事情的,他会按照参数的实际类型进行转发。最后给出一个使用完美转发的泛型模板:

template<typename  Args>
T* Instance(Args&& args)
{
    return new T(std::forward<Args >(args));
}

这个工厂函数的参数是右值引用类型,内部使用std::forward按照参数的实际类型进行转发,如果参数的实际类型是右值,那么创建的时候会自动匹配移动构造,如果是左值则会匹配拷贝构造。

Consumer and Producer

  1. Adopt condition variable(mainly)
  2. Adopt semaphore (deprecated)
#include <thread>
#include <iostream>
#include <mutex>
#include <queue>
#include <semaphore.h>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
std::mutex mx;
sem_t cpSem;
std::queue<int> q;
boost::asio::io_service io;
bool finished = false;
void producer(int n) {
	for(int i=0; i<n; ++i) {
		{
			std::lock_guard<std::mutex> lk(mx);
			q.push(i);
			std::cout << "pushing " << i << std::endl;
		}
		sem_post(&cpSem);
//boost::asio::deadline_timer t(io, boost::posix_time::milliseconds(100));
//t.wait();
	}
	{
		std::lock_guard<std::mutex> lk(mx);
		finished = true;
	}
	sem_post(&cpSem);
}
void consumer() {
	while (true) {
	        sem_wait(&cpSem);
		while (!q.empty()) {
			std::cout << "consuming " << q.front() << std::endl;
			q.pop();
		}
		if (finished) break;
	}
}
int main() {
    sem_init(&cpSem, 0, 0);
	std::thread t1(producer, 10);
	std::thread t2(consumer);
	t1.join();
	t2.join();
	std::cout << "finished!" << std::endl;
}

Another method:

#include <thread>
#include <iostream>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
std::mutex mx;
std::condition_variable cv;
std::queue<int> q;
boost::asio::io_service io;
bool finished = false;
void producer(int n) {
	for(int i=0; i<n; ++i) {
		{
			std::lock_guard<std::mutex> lk(mx);
			q.push(i);
			std::cout << "pushing " << i << std::endl;
		}
		cv.notify_all();
//boost::asio::deadline_timer t(io, boost::posix_time::milliseconds(100));
//t.wait();
	}

	{
		std::lock_guard<std::mutex> lk(mx);
		finished = true;
	}
	cv.notify_all();
}
void consumer() {
	while (true) {
		std::unique_lock<std::mutex> lk(mx);
		cv.wait(lk, []{ return finished || !q.empty(); });
		while (!q.empty()) {
			std::cout << "consuming " << q.front() << std::endl;
			q.pop();
		}
		if (finished) break;
	}
}
int main() {
	std::thread t1(producer, 10);
	std::thread t2(consumer);
	t1.join();
	t2.join();
	std::cout << "finished!" << std::endl;
}

make_share/share_ptr

#include <memory>
#include <string>
class Foo
{
public:
    typedef std::shared_ptr<Foo> Ptr;

    Foo()
    : a(42)
    , b(false)
    , c(12.234)
    , d("FooBarBaz")
    {}
private:
    int a;
    bool b;
    float c;
    std::string d;
};
const int loop_count = 100000000;
int main(int argc, char** argv)
{
    for (int i = 0; i < loop_count; i++)
    {
#ifdef USE_MAKE_SHARED
        Foo::Ptr p = std::make_shared<Foo>();
#else
        Foo::Ptr p = Foo::Ptr(new Foo);
#endif
    }
    return 0;
}

以上代码当使用g++ -o2 main.cc,即可看到make_shared的优势。

Empty a normal array (e.g. int[])

std::fill_n(array, elementCount, 0);

Mutex

lock_guard has no other member function, so it cannot be unlock or lock by hand. On the contrary, unique_lock can do it, like, u.lock();u.unlock(). So the latter is ofter used in condition_variable.

using MuxGuard = std::lock_guard<std::mutex> g(mutex_value);
// or 
std::unique_lock<std::mutex> u(mutex_value)

以上代码将锁定上面代码所包括的域

Condition Variable(条件变量)

Condition variable is a kind of Synchronization primitive used for the communication between different threads.

  • std::condition_variable:必须与std::unique_lock配合使用
  • std::condition_variable_any:更加通用的条件变量,可以与任意类型的锁配合使用,相比前者使用时会有额外的开销

调用wait的时候,该线程(含有该wait)会释放函数传入的锁并同时进入等待,当被唤醒时会重新获得锁,其中
condition_variable cv.wait(lock, pred());相当于while (!pred()) {wait(lock);}cv.wait() and cv.notify_one() always work together. When the lock is unlocked and notify_one() is executed, the thread where wait() stays will be nofitied. In some place, we need to unlock the mutex by hand, for instance, we need to notify one thread(it includes cv.wait()), so we adopt the following in the other thread:

// cv_m is mutex which is locked by both thread
std::unique_lock<std::mutex> lk(cv_m);
// lk is locked via the above line
lk.unlock();
// waiting thread is notified, cv.wait returns
cv.notify_one();
// do something
lk.lock();

这个实例告诉我们多线程有时候比较复杂,该代码会在标记3处死循环,wait一直等待,这证明了2处的notify_one 执行“晚于”4处所在整个域的代码,即先执行4处代码,后worker_thread中的wait函数。解决的方法很多种:

  1. uncomment place 1, 这样4处的wait函数可以获得锁和process (true),继续执行.
  2. uncomment place 3, 这样保证先进入worker_thread,并一直持有锁,此时main函数被锁阻塞,当worker_thread unlock时,main继续执行,此时无需1处的notify_one
    #include <iostream> 
    #include <string> 
    #include <thread> 
    #include <mutex> 
    #include <condition_variable> 
    #include <chrono> 
    std::mutex m;
    std::condition_variable cv;
    std::string data;
    bool ready = false;
    bool processed = false;
    void worker_thread() {
     std::unique_lock<std::mutex> lk(m);
     cv.wait(lk, [] {return ready; });
     std::cout << "Worker thread is processing data\n";
     processed = true;
     lk.unlock();
     // 1.
     //cv.notify_one();
    }
    int main() {
     std::thread worker(worker_thread);
     {
         std::lock_guard<std::mutex> lk(m);
         ready = true;
         std::cout << "main() signals data ready for processing\n";
     }
     // 2.
     cv.notify_one();
     // 3.
     //std::this_thread::sleep_for(std::chrono::seconds(1));
     {   
         std::unique_lock<std::mutex> lk(m);
         // 4.
         cv.wait(lk, [] {return processed; }); 
     }
     std::cout << "Back in main(), data = " << data << '\n';
     worker.join();
     return 0;
    }
    

Const

const iterator or const_iterator

std::vector<int> vec;
-------------------
const std::vector<int>::iterator iter = vec.begin();//iter acts like a T* const
*iter = 10;//OK, changes what iter points
++iter; //error, iter is const
-------------------
std::vector<int>::const_iterator cIter = vec.begin();
*cIter = 10;//error!*cIter is const
++cIter; //fine, changes cIter

const member functions(const成员函数)
member functions(成员函数)被声明为const的目的是标明这个member functions(成员函数)可能会被const objects(对象)调用。因为两个原因,这样的member functions(成员函数)非常重要。首先,它使一个class(类)的interface(接口)更容易被理解。知道哪个函数可以改变object(对象)而哪个不可以是很重要的。第二,它们可以和const objects(对象)一起工作。
member functions(成员函数)在只有constness(常量性)不同时是可以被overloaded(重载)的。

class TextBlock{
public:
	const char& operator[](std::size_t position) const{
	return text[position];}
	char& operator[](std::size_t position){
	return text[position];}
private:
	std::string text;
};

一个const member function(成员函数)不被允许改变调用它的object(对象)的任何non-static data members(非静态数据成员)。

Non-local static variable

The relative order of initialization of non-local statciaovjects defined in different translation units is undefined.

Thread Pool

  1. Why or where do we use thread pool?
    A thread pool is a group of pre-instantiated, idle threads(empty queue, no task) which stand ready to be given work. These are preferred over instantiating new threads for each task when there is a large number of short tasks to be done rather than a small number of long ones. This prevents having to incur the overhead of creating a thread a large number of times.
    |    |——————————————|   |
    |    ↓       |      ↑   |
    | worker[1]  |  | task  |
    | worker[2]<—|  | queue |
    | worker[3]<-|  |       |
    |               |———————|
    |——————————pool—————————|
    
  2. How to accomplish a thread pool?
    As you can see from the above, a thread pool class has several worker threads, which process task popped from task queue. As long as the task queue is not empty, all of worker threads process these tasks simultaneously. So we need to lock queue when it pops a task. At the same time, we should notify worker threads in case the empty queue makes worker threads sleep forever.
    Detailed code can be found on Github from ThreadPool or AVT-GT2000-Driver.

Factory Function with unique_ptr

#include <memory>
#include <iostream>
using namespace std;
// class definition with polymorphic
class Invest{
public:
	Invest(int x) { std::cout << "Invest custom ctor\n"; }
	virtual ~Invest() { std::cout << "Invest deleter\n"; }
};
class Stock : public Invest {
public:
	Stock(int x):Invest(x){ std::cout << "Stock custom ctor\n"; }
	~Stock() { std::cout << "Stock deleter\n"; }
};
class RealEstate: public Invest{
public:
	RealEstate(int x) :Invest(x) { std::cout << "RealEstate custom ctor\n"; }
	~RealEstate() { std::cout << "RealEstate deleter\n"; }
};
// lambda function as deleter
auto deleter = [](Invest* in) {
	std::cout << "custom deleter\n";
	delete in;
};
// factory funciton returning a unique_ptr
template <typename... T>
unique_ptr<Invest, decltype(deleter)> factory(int x, T&&... param) {
	auto p_null = unique_ptr<Invest, decltype(deleter)> (nullptr, deleter);
	switch (x) {
	case 0:
		p_null.reset(new Invest(std::forward<T>(param)...));
		break;
	case 1:
		p_null.reset(new Stock(std::forward<T>(param)...));
		break;
	case 2:
		p_null.reset(new RealEstate(std::forward<T>(param)...));
		break;
	}
	return p_null;
}
// including ctors and dtors
void hello() {
	auto p = factory(1, 4);
}
int main() {
	hello();
	std::cin.get();
}

SFINAE (Substitution Failture is Not An Error)

If we define a function with a template argument, this function will be invoked on all possible types. Type traits and enable_if let us create different functions that act on different kinds of types, while still remaining generic. enable_if is widely used in template overloading declaration. In fact, enable_if works because of SFINAE. For example, the above B becomes false, and then the substitution of T fails and the templace function or class will not produced.

template <bool B, typename T = void>
using enable_if_t = typename enable_if<B, T>::type;
//example class
class Person{
	template <typename T, typename = 
		std::enable_if_t<
			std::is_integral<std::remove_reference_t<T>>::value
			>
		>
}