C++11核心特性


资料
https://en.cppreference.com/w/

构造函数中的成员初始化

在c++11之前, 成员变量的初始化必须放在构造函数上, 例如

class Base{
public:
    Base():x(1){}
public:
    int x;
};

c++11之后, 可以这样写

class Base{
public:
    Base(){}
    Base(int value):x(value){}
public:
    int x = 1; //如果构造函数未赋值,则默认值为1
};

错误的写法

class Base{
public:
    Base(){}
    Base(int value){
    	//错误的做法, x会被赋值两次, 先为1 , 后为value
    	this->x = value; 
    }
public:
    int x = 1; //如果构造函数未赋值,则默认值为1
};

左值与右值

C++11中为了优化对象赋值和移动的效率问题, 将变量的类型区分为左值和右值
右值对象可以认为是即将被抛弃掉的对象, 比如

    int && i = 1;
    Object && obj = Object(); //声明一个右值对象

因此右值用于对象的移动

移动构造和移动赋值函数

因为右值表示对象将被抛弃, 因此可以直接瓜分右值中的资源, 比如

#include <utility>
class Object1{
public:
    Object1(){
        printf("Object1 constructor\n");
    }
	//拷贝构造函数. 本处是深度拷贝
    Object1(const Object1 & obj){
        printf("Object1 copy-constructor\n");
        this->size_ = obj.size_;
        this->memory_ = new int[this->size_];
        memcpy(this->memory_, obj.memory_, sizeof(int)*this->size_);
    }
    //移动构造函数, 将obj的的资源移动到新的对象中
    Object1(Object1 && obj){
        printf("Object1 move-constructor\n");
        this->size_ = obj.size_;
        this->memory_ = obj.memory_;
        obj.size_ = 0;
        obj.memory_ =  nullptr;
    }
    
	//拷贝赋值函数, 未实现
    Object1 & operator = (const Object1 & obj){
        printf("Object1 copy-assign\n");
    }
    //移动赋值函数, 未实现
    Object1 & operator = (Object1 && obj){
        printf("Object1 move-assign\n");
    }
    
    ~Object1(){
        delete[] memory_;
    }

    int size() const{
        return this->size_;
    }

private:
    int size_ = 32;
    int* memory_ = new int[size_];
};

使用

int main(int argc, char *argv[])
{
    Object1 unused;
    printf("[unsued] size: %d\n", unused.size());
    Object1 newobj(std::move(unused)); //将左值强转为右值
    printf("[unsued] size: %d\n", unused.size());
    return 0;
}

右值的退化

先看一个函数

void f2(Object1 && obj){
    auto a = obj; //移动还是拷贝?
}

Object1 && x = Object1();
Object1 y = x; //移动还是拷贝?

答案是: 都是拷贝
因为右值在传递过程中会退化为左值.

我估计c++11这么做, 是为了让代码更健壮吧, 因为右值是即将被抛弃的对象, 多数情况下, 用完一次之后就扔掉, 再次使用这个对象的机会很少. 如果必须这么做, 那么需要强转

#include <utility>
void f2(Object1 && obj){
    auto a = std::move(obj); 
}

Object1 && x = Object1();
Object1 y = std::move(x); //移动还是拷贝?

模板右值类型推断

使用c++时, 模板通常是必不可少的, 例如

template <typename T>
void f3(T && x){
    printf("f3\n");
}

那么问题来了

f3(Object1()); //(1), T && &&

Object1 lvalue;
Object1 & lref = lvalue;
f3(lref);  //(2),  T & &&

Object1 obj;
f3(obj); //(3)

以上三种情况中, T是什么类型? x是什么类型?

C++11使用一种折叠规则
将将入参的类型X代入模板中, 然后再简化. 如下规则

  1. X & &, X & &&, X && &, 折叠为X &
  2. X && &&折叠为X &&
  3. 左值传给右值引用参数时, 编译器将T推断为左值引用

以上面的代码为例子

//传入一个右值
f3(Object1()); //T为Object1 &&, x类型为Object1 && &&, 折叠为Object1 &&

Object1 lvalue;
Object1 & lref = lvalue;
//传入一个左值引用
f3(lref);  //T为Object1 &, x类型为Object1 & &&, 折叠为Object1 &

//传入一个左值
Object1 obj;
f3(obj);  // T为Object1 &, x类型为Object1 &

参数转发

从上节的模板右值类型推断, 可以出, 模板中的的参数类型是不定的, 再加上右值退化问题, 情况更复杂. 比如下例

template <typename T>
void f2(T && x){
    printf("f2\n");
}
template <typename T>
void f3(T && x){
    printf("f3\n");
    f2(x);
}

f3的参数转发给f2后, 很可能就导致参数的引用类型不一致, 解决措施是使用std::forward

#include <utility>
template <typename T>
void f3(T && x){
    printf("f3\n");
    f2(std::forward<T>(x));
}

可变参数模板

template<typename...Args>
void f7(Args&&... args){
    printf("sizeof Args: %d\n", sizeof...(Args));
    printf("sizeof args: %d\n", sizeof...(args));
}

这个例子没有任何用处, 因为无法处理参数, 需要将参数展开

template<typename T>
void f8(T && t)
{
    std::cout<<"t= " << t << std::endl;
}

template<typename T, typename...Args>
void f8(T && t, Args&&... args)
{
   std::cout<<"t= " << t << std::endl;
   f8(std::forward<Args>(args)...);
}

可调用对象

  1. 函数, 很明显, 函数就是给别人调用的
  2. 函数指针, c语言就有
  3. lambda表达式, 一种语法糖, 由编译器生成的一种可调用对象
  4. bind创建的对象, 把函数或成员函数, 再加上参数, 封装成一个可调用对象
  5. 重载了函数调用运算符的对象

Lambda

Lambda是一个可调用对象, 对象的class是由编译器自动生成的
简单的例子

#include <functional>

void f5(std::function<void ()> && f){
    printf("f5\n");
    f();
}

int main(int argc, char *argv[])
{
    Object1 obj;
    int x = 10;
    //编译器自动生成了一个class, 并将x, y赋值给这个class的内部成员变量.
    f5([x, obj](){ //值捕获, 执行了一次拷贝构造和一次移动构造, 很显然, 这不高效
        printf("x=%d\n", x);
    });

    f5([x, &obj](){ //引用捕获
        printf("x=%d\n", x);
    });
    
    f5([=](){ //隐式的值捕获
        printf("x=%d\n", x);
    });
    f5([&](){ //隐式的引用捕获
        printf("x=%d\n", x);
    });
    
    //也可以混着使用
    f5([=, &obj](){ //obj使用引用获取, 其它的默认为隐式的值获取
        printf("x=%d\n", x);
    });
    
    f5([&, x](){ //obj使用引用获取, 其它的默认为隐式的值获取
        printf("x=%d\n", x);
    });
    
	auto f = []{return }
}

显示指定lambda的返回值类型

//显示声明返回值类型为int
auto f = []()->int{ return 10;} ;
printf("f()= %d\n", f());

bind可调用对象

普通函数转换成可调用对象

void f6(int x, const std::string & s){
    std::cout<<"x = " <<x << ", s = " << s << std::endl;
}

int main(int argc, char *argv[])
{
	//普通的调用方式
	f6(1, "123");

	//将f6, 1, "123", 揉成一个可调用对象
	auto f = std::bind(f6, 1, "123");
    f(); //内部将调用f6(1, "123");

	//使用参数占位符
	//生成的对象有一个入参
    auto f2 = std::bind(f6, 1, std::placeholders::_1);
    f2("456"); //内部调用内部将调用f6(1, "456");
}

class的成员函数转换成可调用对象

class Invoker
{
public:
    void call(int x, const std::string & s){
        std::cout<<"x = " <<x << ", s = " << s << std::endl;
    }
};
int main(int argc, char *argv[])
{
	{
	    auto f = std::bind(&Invoker::call, 
	    std::placeholders::_1, 
	    std::placeholders::_2, 
	    std::placeholders::_3);
	    f(Invoker(), 1, "hello"); //等同于Invoker().call(1, "hello");
	}
	{
		Invoker invoker;
	    auto f = std::bind(&Invoker::call, invoker
	    std::placeholders::_1, 
	    std::placeholders::_2);
	    f(1, "hello"); //等同于invoker.call(1, "hello");
	}
}

常用迭代器

map

std::map<std::string, std::string> map;
map["k1"] = "v1";
map["k2"] = "v2";

//for迭代, 隐式调用迭代器
for(std::pair<const std::string, std::string> &pair : map){
    std::cout << "key = " << pair.first << ", value = " << pair.second << std::endl;
}

//显示调用迭代器
auto it = map.begin(); //it的类型很复杂, 要是没有auto, 那代码得老长老长了
while(it != map.end()){
    std::cout << "key = " << it->first << ", value = " << it->second << std::endl;
    it++;
}

list

std::list<std::string> list;
list.push_back("1");
list.push_back("2");
for(auto & item : list){
    std::cout << item << std::endl;
}

猜你喜欢

转载自blog.csdn.net/wzj_whut/article/details/87911409