C++11特性——新关键字

󰃭 2016-04-26

C++11特性——新关键字


为了提高语言的功能性、便利性以及准确性特性,c++11标准制定的时候增加了一些新的关键字,并对某些已有的关键字的语义进行了扩充或者重新定义。以下将为大家介绍这些关键字以及它们带来的改进。

alignas/alignof

alignas和alignof是新关键字,用来指定用户自定义结构或者变量的对齐方式,这一定程度上增强了c++的移植性。对于alignas,可以接受的参数有自然数,常数表达式,常函数以及类型。alignas可以嵌套使用,但是编译器遵循最严格的说明符,将会采用最大的说明符进行对齐。

    struct alignas(int) _S {
    	char a;
    	short b;
        alignas(8) char c[3];
    };
    cout << sizeof(_S); //16
    //offsets: _S::a = 0;_S::b = 2;_S::c = 8
    cout << alignof(_S); //8

注意:alignas的对齐不会解决封装的问题,也就是说alignas不能实现比目标体系结构小的pack,如果有需要,请使用#pragma pack(n)。

    struct alignas(1) _S {
    	char a;
        short b;
    };
    cout << sizeof(_S); // 4 in my system

auto

auto是一个语义修改关键字。在c++11之前的标准中,auto用来标明一个具有自动生存期的变量(包括在寄存器中)。在c++11中,auto不再用来标示自动生存期,而是用来进行语义推断。

    #include<iostream>
	using namespace std;
	
	struct complex {
    	float re;
    	float im;
	};
	auto add(const complex a, const complex b) -> complex {
    	complex c;
    	c.re = a.re + b.re;
    	c.im = a.im + b.im;
    	return move(c);
        }
	int main() {
    	complex a = {1.2f, 2.3f};
    	complex b = {1.2f, -2.3f};
    	auto c = add(a,b);
    	cout << "c = (" << c.re << "," << c.im << ")" << endl;
	}

constexpr

constexpr是一般化常量表达式,一定程度上对之前标准中的常量表达式的扩展。在新标准中,编译器不仅仅能识别之前的运算符,如3+4,还可以识别函数、类等常量表达式。
要作为常量表达式,需要满足以下几个要求:
1.变量

  • 字面量类型
  • 立即进行构造或者赋值
  • 构造参数必须是字面值、常变量或者函数
  • 构造对象的构造函数必须满足常量表达式构造函数要求,如果是explicit函数,必须使用constexpr修饰

2.函数

  • 不能是虚函数
  • 返回值为字面量类型
  • 参数为字面量类型

3.构造函数

  • 参数为字面量类型
  • 不能有虚基类
  • 不能用function-try-block
	#include<iostream>
    using namespace std;

    struct complex {
        explicit constexpr complex(const float r, const float i) : re(r), im(i) {}
        constexpr float real() {return re;}
        constexpr float imag() {return im;}
    private:
        float re;
        float im;
    };

    int main() {
        constexpr complex I(0.1f, 0.2f);
        const float c = I.real();
        cout << "const float c = constexpr; " << c << endl;
        return 0;
    }

decltype

decltype用来推导变量或者函数的类型,根据表达式的返回结果的不同,decltype还会得到xvalue,prvalue和lvalue的区分。

    #include<iostream>
    #include<typeinfo>
    using namespace std;

    struct complex {
        float re;
        float im;
    };

    auto real_add(complex a, complex b) -> decltype(a.re + b.re) {
            return a.re + b.re;
    }

    int main() {
        complex I = {1.1f, 1.2f}, J = {2.2f, 3.3f};
        cout << typeid(decltype(I.re)).name() << endl;
        auto f = [](complex a, complex b) -> float {
            return a.im + b.im;
        };
        decltype(f) imag_add{f};
        cout << "imag_add(I,J) = " << imag_add(I, J) << endl;
        return 0;
    }

default/delete

default/delete在c++11标准中增加了新语义,针对类的构造函数、拷贝、移动、析构等函数进行描述。default标示使用默认函数,delete标示弃用,使用delete标示的函数在编译器报错。

	class A {
    public:
    	A() = default; //ok, only here
        //A( const int &) = default; // error
        A(const int &) = delete; //ok
    private:
    	int val;
    };
    
    A a(1); //error, deleted

export

在c++11标准中弃用。 ##noexcept noexcept用来标示函数是否抛出异常,可以是表达式或者嵌套的noexcept。

    template <class T>
    void foo0() noexcept(noexcept(T())) {}
    void foo1() noexcept(true){}
    void foo2() noexcept {}
    template <int num>
    void foo3() noexcept(num > 0) {}

nullptr

空指针。他是std::nullptr_t类型的一个prvalue。在c++11之前,NULL定义如下:

	#define NULL 0

这对于c++的多态语法一直有一个隐患,比如:

void f(int a);//1
void f(int *a);//2
f(NULL);

在这种情况下,我们希望采用2的方法来进行处理,但编译器进行宏展开之后选用的确是1。c++11定义了nullptr,并令NULL宏展开为nullptr,使其成为一个纯指针,防止编译器进行转化,保证了语义的一致性。

static_assert

在很多时候,针对不同环境我们可以写一些来代码进行相应的策略处理,甚至有时候我们根据一些常量或者常量表达式进行某些操作或者模板编程情况下需要进行一些代码检查,在这种情况下我们就可以使用static_assert来辅助编译。

    static_assert(sizeof(void *) == 4, "64-bit code generation is not supported.");
    template <class T, class U>
    class MyClass {
    static_assert(!std::is_empty<T>::value, "T should not be empty.");
    static_assert(!std::is_class<U>::, "U should be a class.");
    private:
   		T tval;
        U uval;
    }

thread_local

thread_local是和c++多线程一同引进的关键字,他只能对命名空间下的变量,局部变量,或者静态变量进行修饰,表示变量拥有线程范围内的生存期。他可以和static或者extern来联合使用,可以改变变量的链接属性,但是并不影响他的生存期。

    #include<iostream>
    #include<thread>
    #include<mutex>
    using namespace std;

    thread_local int global_num = 0;
    mutex cout_mutex;

    void global_add()
    {
        global_num++;
        {
            lock_guard<mutex> lock(cout_mutex);
            cout << "thread id: " << this_thread::get_id() << ", global_num = " << global_num << endl;
        }
    }

    void local_add()
    {
        thread_local int local_num = 0;
        {
            lock_guard<mutex> lock(cout_mutex);
            cout << "thread id: " << this_thread::get_id() << ", local_num = " << local_num << endl;
        }
        local_num++;
    }

    void local_multi()
    {
        int times = 3;
        while(times--) {
            local_add();
        }
    }

    int main()
    {
        global_num ++;//1
        global_add();//2
        std::thread t1(global_add);//3
        std::thread t2(global_add);//4
        t1.join();
        t2.join();
        local_multi();//5
        std::thread t3(local_add);//6
        std::thread t4(local_add);//7
        t3.join();
        t4.join();
        return 0;
    }

上面的例子得到的结果为:

    thread id: 140233009923904, global_num = 2
    thread id: 140232993167104, global_num = 1
    thread id: 140232984774400, global_num = 1
    thread id: 140233009923904, local_num = 0
    thread id: 140233009923904, local_num = 1
    thread id: 140233009923904, local_num = 2
    thread id: 140232984774400, local_num = 0
    thread id: 140232993167104, local_num = 0

可以看到,global_num在1处被修改,但影响的只有2处函数调用,即只影响主线程的调用,不影响3,4中申请的线程操作;在5中对local_add()调用了三次,作为函数内部的局部变量的生存期仍然是线程持续期(函数local_multi的执行期),并且不影响6和7申请的线程操作。

using

在c++11中,using扩充了一个新语义——类型别名。using不仅有typedef的作用,还可以对模板进行别名操作。

    using func = void (*)(int , int);
    func f = [](int , int) ->void {};
    f(1,1);
    using my_flags = std::ios_base::fmtflags;
    my_flags mf = std::ios_base::dec;
    template <class T> using ptr = T *;
    ptr<int> pa;
    //template <class T> typedef T* ptr; ==>error

关于c++11标准的关键字特性大概说到这里,对于其他的一些特性将在之后的文章中论述。