c++ 11 6

  • lambda使用注意事项
  • 基于任务而不是线程
  • 如果move廉价就传值,并且永远复制
  • 尽可能使用emplace
  • 补充bind实现,C++thread, 异步编程,logic_error()异常,硬件线程(核数目),软件线程(排队,系统层),std::tread 语言层。,joinable可连接, join,Detach, conditioanl variable 和mutex的使用 ,promise

lambda使用注意事项

1.避免误捕捉

class Item{
public:
    Item() {cout << "default ctor" << endl;}
    Item(const Item& item) {cout <<"copy ctor"<<endl;}
    void display () const {cout << "display" << endl;}
};

int main(){
    Item item;
    auto cap_by_ref = [&](){item.display();};
    auto cap_by_val = [=](){item.display();};//此处需要注意两点1. display必须为const的因为此处为按值传递 右值默认含const属性 必须调用const函数。或者这个lambda也可以改为mutable。2.此处按值传递会调用一次拷贝构造函数。
    cap_by_ref();
    cap_by_val();
    }

2.传引用会有很多问题,要时刻注意作用域的问题

using FilterContainer = vector<function<bool(int)>>;
int computerDivisor(void){
    return 1024;
}   
FileterContainer creatFilters(void) {
    auto divisor = computerDivisor();
    FilterContainer filters;
    filters.emplace_back(
        [&](int value){
            cout << divisor << endl;
            return value %divisor ==0;
        },
    );
    return filters;
}

int main(){
    FilterContainer filters = creatFilters();
    cout << filters[0](4096) << endl; //输出结果本应该为打印1024 ,1,但是会出现乱码
    //这是因为push_back的lambda函数是按引用传递的,divisor为一个局部变量 返回后就不能保证状态,所以此处lambda应该改用按值传递 [=](){};
}

3.类内部捕捉不到成员变量,必须加this->_data;
小心 static

using FilterContainer = vector<function<bool(int)>>;
int main(){
    FileterContainer filters;
    static int x= 200;
    filters.emplace_back(
        [=](int value){
            cout <<x<<endl;//此处不会捕捉static变量,但是可以看到static变量
            return value%x==0;
        }
    );
    filters[0](400);
    x += 1;
    filters[0](400);
    //结果本应200,200,但是结果是200,201 也就是说闭包不会捕捉static变量。只会补货分静态局部变量
    c++14可以
    [_x = move(x)](int value){return value%_x==0;}
    闭包使用静态变量时应该先将其复制下来再传递,类内使用闭包也是一样。   
}

4.使用初始化列表转移对象,把需要捕获的对象放到初始化列表中。此处初始化列表指的是[]中的

class Item{
public:
    Item(){cout <<"default ctror"}
    Item(const Item& item){cout << "slow copy "<< endl;}
    Item(Item&& item) { cout << "fast copy" << endl;}
    void display() const {cout << "item" << endl;}
};

int main(void){
    Item item_1,item_2;
    auto f_1 = [_item1 = move(Item_1),_item2 = move(Item_2)] (){
        _item1.display();
        _item2.display();
    };
    auto f_2 = [_item_1 = make_unique<Item>(),_item_2 = make_unique<Item>()]{
        _item_1.display();
        _item_2.display();

    };

}

5.对通用引用和完美转发使用decltype()

template<typename T> void func_impl(T _t){
    cout << sizeof(T) << endl;
}

int main(){
    auto f = [](auto v){
        func_impl(v);
    };
    f(100);
}

//对通用引用
template<typename T> void func_impl(T _t){
    cout << sizeof(T) << endl;
}

template<typename T> func_proxy(T&& t){
    func_impl(forward<T>(t));
}
class Item{
public:
    Item(){}
    Item(Item&& item) {cout << "move ctor" << endl;}
}
int main(){
    //lambda 做泛型右值引用需要转发时不知道对象类型此时可以用decltype()推导
    auto f = [](auto&& v){
        func_proxy(forward<decltype(v)>(v));
    };
    f(100);
}

6.尽量使用lambda表达式而不是bind

//bind 用来实现函数对象
//需要using std::palcehoders
void func(int x,int y){
    cout << "x"<<x<<"y" << y<<endl;
}

int main(){
    function<void(void)> f = bind(func,100,200);//void(void)表示绑定后不用传递任何参数了
    function<void(int)> f_1= bind(func,100,_1);
    function<void(int,int)> f_2 = bind(func,_2,_1);

    //此处都是by-value传递进去的
}

class Item{
public:
    Item(){cout << "Default ctor"<<endl;}
    Item(Item& item){cout << "copy ctor"<<endl;}
};  
void func(const Item& item){
    cout << "func" << endl;
}

int main(){

    Item item;
    auto f = bind(func,item);//此时按值传递仍会调用copy ctor.
    f();//此处item时安引用传递
}

用lambda可读性更好,更灵活可以选择按值安引用传递

//判断一个输在范围内部
int main(){
    int low = 100,high=200;
    auto betweenfunclambda = [low,high](int val) {
        return (low<=val)&&(val<=hign);
    }

    betweenfunclambda(150);
    auto betweenBind = bind(
    logic_and<bool>(),bind(
    less_equal<int>(),low,_1),
    bind(less_equal<int>(),_1,high)
    );
    betweenBind(150);
    //对比lambda和bind
}


//例2
int getValue(void){
    return 1024;
}

int func(int x,int y){
    return x*y;
}

int main(){
    //bind
    auto fb = bind(func,getValue(),_1);
    fb(256);//getValue 在绑定时就已经执行了

    //lambda
    auto fl = [](int y){
        return func(getValue(),y);
    }

    fl();//此时调用时才会调用getValue();
}

重载函数的匹配,bind 无法匹配,使用lambda

func(int x){}
func(int x, int y){}

int main(){
    auto fl = [](){
        return func(100,200);//ok
    }
    fl();
    auto fb = bind(func,100,200);//编译失败不知道调用哪一个重载版本,除非显式的声明
    fb();//
    std::function<void(void)> fb = std::bind(static_cast<void (*)(int x,int y)> (func),100, 200);//强制转换才行
}

编译的时候可能lambda效率会更高。
所以尽量使用lambda而不是bind

基于任务而不是线程

1.使用任务对于异常处理要优于直接使用线程

int  aoAsyncWork(int x,int y){
    return x*y;
}

int main(){
    thread t(aoAsyncWork,200);//开了新线程 出了异常 本线程无法捕捉,所以此处加try catch没用
    t.join();//线程版

    auto f  = async(aoAsyncWork, 100, 200)// auto==> future<int> 异步版
    try{
    cout << f.get() << endl;//采用异步虽然有可能工作在其他线程但是会将状态不存在一个共享区,可以返回并且catch到
    }
    catch(exception& e){
        cout << e.what() << endl;
    }
}

异步任务就将线程封装了,不在乎是否真的创建线程。

2.异步执行,请显示指定策略

//async与defered策略 显式的指定策略 (deferred 延迟 还是 aync异步 )


void fsleep(void){

    this_thread::sleep_for(2s);
    cout << "fsleep" << endl;
}

int main(){

    auto f = async(launch::deferred, fsleep);//显式指定延迟运行
    cout<< "before f.get()" << endl;//加了deferred表示指定真正运行f.get()时才运行fleep
    f.get();
    cout <<"After f.get()" << endl;
}

async 使用方法

int  main(void){

    auto f= async(launch::async,sleep);
    while(f.wait_for(10ms) != future_status::ready)
    {
        cout << "sleep sometime" << endl;
        this_thread::sleep_for(50ms);
    }

    cout << "task done" << endl;//会顺序执行, 
}

int  main(void){

    auto f= async(launch::deferred,sleep);//改成deferred 永远不会打印task done.使用async 会自动运行并且在运行结束更改future_status状态为ready,但是指定deferred延迟执行只有显式执行f.get()才会执行 。调用完结束后 不能再调用 f.sleep() 了。
    while(f.wait_for(10ms) != future_status::ready)
    {
        cout << "sleep sometime" << endl;
        this_thread::sleep_for(50ms);
    }

    cout << "task done" << endl;// 
}
//可以改成
int main(){
    auto f= async(launch::deferred,sleep);
    if (f.wait(0ms) == future_status::deferred){
        cout << "It's a deferred call"<< endl;
        f.get();
    }
    else{
        while(f.wait_for(500ms) != future_status::ready)
        {
            cout << "sleep sometime" << endl;
            this_thread::sleep_for(50ms);
        }


    }
}

2.在路径终点使std::thread 不可连接
使用 RAII的方式选择解除链接的方式。防止主进程意外结束,子进程不能正常运行。

class ThreadRAII{
public:
    enum class DctorAction{join,detach}

    ThreadRAII(thread&& t,DctorAction action)_action(action),_t(move(t)){

    }
    ~ThreadRAII(){
        if (_t.joinable()){//如果可连接,变成不可连接可以使用join 或者 detach
            if (DctorAction::join == _action) {
                cout << "join" << endl;
                _t.join();
            }
            else{
                cout <<"detach"<< endl;
                _t.detach();
            }
        }
    }
    thread& get(void) {return _t;}
protected:
    DctorAction _action;
    thread _t;
};
//将线程封装起来就可以在析构的时候选择怎么结束。

void proc(){

    this_thread::sleep(3s);
    cout << "proc" << endl;
}

int main(){
    ThreadRAII tr(thread(proc),ThreadRAII::DctorAction::join);//析构的时候join
    cout<< "Done" << endl;
    ThreadRAII tr(thread(proc),ThreadRAII::DctorAction::detach);//析构的时候join
}

注意线程句柄不同行为的析构
使用aync策略的异步任务,在析构f时有可能因为子线程还在引用shared state,造成 f无法析构阻塞。
使用无返回值的future进行一次性事件通知

std::conditioanl_variable cv;
std::mutex m;
{
    cv.notify_one();
}
 {
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk);//1.此处在cv.wait之前有可能cv.notiy_one()已经调用过一次了,并且有可能以后不会调用这就造成死锁.无法保证nofity一定在wait之前调用 2. 有可能系统原因误触发此处可以加lambda表达式判断一下cv.wait(lk,[](){check_point});
 }

//改进
void notify(){
    promise<void> p;//使用set_value来代替conditial,可以灵活通知处理。
    ThreadRAII tr(thread([p]{p.get_future().wait();react();}),ThreadRAII::DctorAction::join)
    p.set_value();//void类型不需要指定什么只是一个信号操作。
    }

问题 1.在p.set_value();之前 ThreadRAII tr之后抛出异常当前线程必须结束时 p.get_future().wait()会一直等下去,线程无法正常结束。
所以要确保异常出现时的补救措施

//改进
void react(){
    cout << "react" <<endl;
}

int main(){
    promise<void> p;
    auto sf = p.get_future().share();
    vector<thread> vt;
    for(int i=0;i < 5; ++i){
        vt.emplace_back([sf]{
            sf.wait();
            react();
        })
    }
    cout <<"before set_value" << endl;
    p.set_value();//不加set_value 会死锁在wait()
    for (auto& t: vt){
        t.join();
    }
}

3.对特殊内存使用volatile,对并发操作使用std::atomic
使用atomic
1.并行操作串行化
2.使用volatile阻止读优化。

atomic<int> i;
i = 1;
++i; //threaA
++i; //threadB
//i = 3 一定是串行的原子操作比mutex高效

volatile int i;//一般外设值会在内存中有对应如果不加volatile 会造成编译器优化直接读内存的值不会读外设造成不一致。
++i //此处能保证初始值一定是从原始位置读的,++之后写回去的值就没法保证。
//所以此处要保证操作线性,每次都从物理地址读要加atomic
atomic<volatile int> i;

如果move廉价就传值,并且永远复制

Consider pass by value for copyable parameters that cheap to move and always copied.

vector<string> names;

template<typename T> void addName(T&& newName){
    names.push_back(forward(newName));
}

addName("abc");//不会将"abc" 隐式转化为string。会实例化为 const char* 造成代码膨胀

相反不用通用引用

void addName(string& name){
    ...
}

void addName(string&& name){
    ...
}

对于这种情况就是上面说的move操作很cheap可以通过传值的方式传递,而不会带来模板实例化造成的代码膨胀


void addName(string name){//参数传递中并不会调用多次ctor,copy ctor 在c++11/14中都会优化掉。
    names.push_back(move(name));
}
//例如 :
class Item {
public:
    Item(){cout << "ctor" << endl;}
    Item(const Item& item) {cout <<"copy ctor"<< endl;}
    Item(Item&& item) {cout << "move ctor"<< endl;}
    void display(){cout <<"Display" << endl;}
};

void proc(Item item){
    item.display();
}

Item createItem(void){
    return Item();
}

int main(){
    Item item;
    proc(item);
    proc(createItem());

}

输出结果:
ctor
copy ctor
display
ctor
display
2 
class Password{
public:
    explict Password(string pwd):text(move(pwd)){}
    void changeTo(const string& newpwd){text = newpwd;}//此处最坏的情况下要做三部 1.deallocate2.alloc3.copy最好的情况只要copy就可以了。(因为要判断复制所占的内存是否大于已有内存)
    void changeTo(string newPwd){text = move(newpwd);}
    //此处的开销就固定了1.alloc,2copy,3 move move可以忽略。所以相比上面的情况有可能效率更高。
protected:
    string text;
};

可见在proc(createItem());中并不会调用多次ctor,copy ctor 在c++11/14中都会优化掉。
所以move成本低的情况下可以传值这样会有好处,最直观的就是如果使用通用模板将会造成代码膨胀。

但是,在继承的情况下由于多态性的问题还是要按引用传递否则无法构成多态。

例如:
class Base{
public:
    virtual display(){cout << "base"<<endl;}

};

class Derived:public Base{
public:
    virtual display(){cout << "Derived"<<endl;}

};

void proc(Base b){
    b.display();
}

void Proc2(Base& b){
    b.display();
}
int main(){
    Derived d;
    proc(d);//按值传递会造成切片将继承类部分切掉。
    proc2(d);
}

输出结果:   
    Base    
    Derived

2.尽可能使用emplace
1.insert成本
2.emplace 优势
3.看一下源码 :emplace 是将参数按照不定模板参数传递给适当的构造函数不会有临时变量

class Item{
public:
    explicit Item(const char*str){
        cout << "ctor from" << str << endl;
    }
    Item(const Item& item){
        cout << "copy ctor" << endl;
    }
};

int main(){
    vector<Item> vi;

    vi.push_back("abc");//编译失败不能隐式转换,即使可以隐式转换也还是会先构造临时变量再拷贝构造。
    vi.push_back(Item("abc"));// 先构造临时变量,在copy ctor
    vi.emplace_back("abc");//编译成功,只会构造,将abc当做参数传递给构造函数,使用不定参数模板将参数传给构造函数,不会先产生临时变量。
    Item item;
    vi.emplace_back(item);//此时会调用拷贝构造,所以效率不会比push_back低最差也一样。
}   //一个缺点
    vi.emplace_back(item,1234);知道编译时才会报错,静态分析不会出错。因为是不定参数传递。

全部内容来自effective mordern c++

补充bind实现,C++thread, 异步编程,logic_error()异常,硬件线程(核数目),软件线程(排队,系统层),std::tread 语言层。,joinable可连接, join,Detach, conditioanl variable 和mutex的使用 ,promise

  1. 补充bind实现
    http://blog.csdn.net/eclipser1987/article/details/24406203
    http://blog.csdn.net/zhouguoqionghai/article/details/45770523

其他待补充…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值