求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
要资料
 
追随技术信仰

随时听讲座
每天看新闻
 
 
C++并发编程(中文版)
前言
第1章 你好,C++的并发世界!
1.1 何谓并发
1.2 为什么使用并发?
1.3 C++中的并发和多线程
1.4 开始入门
1.5 本章总结
第2章 线程管理
2.1 线程管理的基础
2.2 向线程函数传递参数
2.3 转移线程所有权
2.4 运行时决定线程数量
2.5 识别线程
2.6 本章总结
第3章 线程间共享数据
3.1 共享数据带来的问题
3.2 使用互斥量保护共享数据
3.3 保护共享数据的替代设施
3.4 本章总结
第4章 同步并发操作
4.1 等待一个事件或其他条件
4.2 使用期望等待一次性事件
4.3 限定等待时间
4.4 使用同步操作简化代码
4.5 本章总结
第5章 C++内存模型和原子类型操作
5.1 内存模型基础
5.2 C++中的原子操作和原子类型
5.3 同步操作和强制排序
5.4 本章总结
第6章 基于锁的并发数据结构设计
6.1 为并发设计的意义何在?
6.2 基于锁的并发数据结构
6.3 基于锁设计更加复杂的数据结构
6.4 本章总结
第7章 无锁并发数据结构设计
7.1 定义和意义
7.2 无锁数据结构的例子
7.3 对于设计无锁数据结构的指导建议
7.4 本章总结
第8章 并发代码设计
8.1 线程间划分工作的技术
8.2 影响并发代码性能的因素
8.3 为多线程性能设计数据结构
8.4 设计并发代码的注意事项
8.5 在实践中设计并发代码
8.6 本章总结
第9章 高级线程管理
9.1 线程池
9.2 中断线程
9.3 本章总结
第10章 多线程程序的测试和调试
10.1 与并发相关的错误类型
10.2 定位并发错误的技术
10.3 本章总结
第11章 C++11语言特性简明参考(部分)
11.1 右值引用
11.2 删除函数
11.3 默认函数
11.4 常量表达式函数
11.5 Lambda函数
11.6 变参模板
11.7 自动推导变量类型
11.8 线程本地变量
 

 
目录
常量表达式函数
作者:Anthony Williams  译者:陈晓伟
37 次浏览
5次  

A.4 常量表达式函数

整型字面值,例如42,就是常量表达式。所以,简单的数学表达式,例如,23x2-4。可以使用其来初始化const整型变量,然后将const整型变量作为新表达的一部分:

  1. const int i=23;
  2. const int two_i=i*2;
  3. const int four=4;
  4. const int forty_two=two_i-four;

使用常量表达式创建变量也可用在其他常量表达式中,有些事只能用常量表达式去做:

指定数组长度:

  1. int bounds=99;
  2. int array[bounds]; // 错误,bounds不是一个常量表达式
  3. const int bounds2=99;
  4. int array2[bounds2]; // 正确,bounds2是一个常量表达式

 

指定非类型模板参数的值:

  1. template<unsigned size>
  2. struct test
  3. {};
  4. test<bounds> ia; // 错误,bounds不是一个常量表达式
  5. test<bounds2> ia2; // 正确,bounds2是一个常量表达式

 

对类中static const整型成员变量进行初始化:

  1. class X
  2. {
  3. static const int the_answer=forty_two;
  4. };

 

对内置类型进行初始化或可用于静态初始化集合:

  1. struct my_aggregate
  2. {
  3. int a;
  4. int b;
  5. };
  6. static my_aggregate ma1={forty_two,123}; // 静态初始化
  7. int dummy=257;
  8. static my_aggregate ma2={dummy,dummy}; // 动态初始化

 

静态初始化可以避免初始化顺序和条件变量的问题。

这些都不是新添加的——你可以在1998版本的C++标准中找到对应上面实例的条款。不过,新标准中常量表达式进行了扩展,并添加了新的关键字——constexpr。

constexpr会对功能进行修改,当参数和函数返回类型符合要求,并且实现很简单,那么这样的函数就能够被声明为constexpr,这样函数可以当做常数表达式来使用:

  1. constexpr int square(int x)
  2. {
  3. return x*x;
  4. }
  5. int array[square(5)];

在这个例子中,array有25个元素,因为square函数的声明为constexpr。当然,这种方式可以当做常数表达式来使用,不意味着什么情况下都是能够自动转换为常数表达式:

  1. int dummy=4;
  2. int array[square(dummy)]; // 错误,dummy不是常数表达式

dummy不是常数表达式,所以square(dummy)也不是——就是一个普通函数调用——所以其不能用来指定array的长度。

A.4.1 常量表达式和自定义类型

目前为止的例子都是以内置int型展开的。不过,在新C++标准库中,对于满足字面类型要求的任何类型,都可以用常量表达式来表示。

要想划分到字面类型中,需要满足一下几点:

  • 一般的拷贝构造函数。

  • 一般的析构函数。

  • 所有成员变量都是非静态的,且基类需要是一般类型。

  • 必须具有一个一般的默认构造函数,或一个constexpr构造函数。

后面会了解一下constexpr构造函数。

现在,先将注意力集中在默认构造函数上,就像下面清单中的CX类一样。

清单A.3(一般)默认构造函数的类

  1. class CX
  2. {
  3. private:
  4. int a;
  5. int b;
  6. public:
  7. CX() = default; // 1
  8. CX(int a_, int b_): // 2
  9. a(a_),b(b_)
  10. {}
  11. int get_a() const
  12. {
  13. return a;
  14. }
  15. int get_b() const
  16. {
  17. return b;
  18. }
  19. int foo() const
  20. {
  21. return a+b;
  22. }
  23. };

 

注意,这里显式的声明了默认构造函数①(见A.3节),为了保存用户定义的构造函数②。因此,这种类型符合字面类型的要求,可以将其用在常量表达式中。

可以提供一个constexpr函数来创建一个实例,例如:

  1. constexpr CX create_cx()
  2. {
  3. return CX();
  4. }

 

也可以创建一个简单的constexpr函数来拷贝参数:

  1. constexpr CX clone(CX val)
  2. {
  3. return val;
  4. }

 

不过,constexpr函数只有其他constexpr函数可以进行调用。CX类中声明成员函数和构造函数为constexpr:

  1. class CX
  2. {
  3. private:
  4. int a;
  5. int b;
  6. public:
  7. CX() = default;
  8. constexpr CX(int a_, int b_):
  9. a(a_),b(b_)
  10. {}
  11. constexpr int get_a() const // 1
  12. {
  13. return a;
  14. }
  15. constexpr int get_b() // 2
  16. {
  17. return b;
  18. }
  19. constexpr int foo()
  20. {
  21. return a+b;
  22. }
  23. };

注意,const对于get_a()①来说就是多余的,因为在使用constexpr时就为const了,所以const描述符在这里会被忽略。

这就允许更多复杂的constexpr函数存在:

  1. constexpr CX make_cx(int a)
  2. {
  3. return CX(a,1);
  4. }
  5. constexpr CX half_double(CX old)
  6. {
  7. return CX(old.get_a()/2,old.get_b()*2);
  8. }
  9. constexpr int foo_squared(CX val)
  10. {
  11. return square(val.foo());
  12. }
  13. int array[foo_squared(half_double(make_cx(10)))]; // 49个元素

 

函数都很有趣,如果想要计算数组的长度或一个整型常量,就需要使用这种方式。最大的好处是常量表达式和constexpr函数会设计到用户定义类型的对象,可以使用这些函数对这些对象进行初始化。因为常量表达式的初始化过程是静态初始化,所以就能避免条件竞争和初始化顺序的问题:

  1. CX si=half_double(CX(42,19)); // 静态初始化

当构造函数被声明为constexpr,且构造函数参数是常量表达式时,那么初始化过程就是常数初始化(可能作为静态初始化的一部分)。随着并发的发展,C++11标准中有一个重要的改变:允许用户定义构造函数进行静态初始化,就可以在初始化的时候避免条件竞争,因为静态过程能保证初始化过程在代码运行前进行。

特别是关于std::mutex(见3.2.1节)或std::atomic<>(见5.2.6节),当想要使用一个全局实例来同步其他变量的访问时,同步访问就能避免条件竞争的发生。构造函数中,互斥量不可能产生条件竞争,因此对于std::mutex的默认构造函数应该被声明为constexpr,为了保证互斥量初始化过程是一个静态初始化过程的一部分。

A.4.2 常量表达式对象

目前,已经了解了constexpr在函数上的应用。constexpr也可以用在对象上,主要是用来做判断的;验证对象是否是使用常量表达式,constexpr构造函数或组合常量表达式进行初始化。

且这个对象需要声明为const:

  1. constexpr int i=45; // ok
  2. constexpr std::string s(“hello”); // 错误,std::string不是字面类型
  3. int foo();
  4. constexpr int j=foo(); // 错误,foo()没有声明为constexpr

A.4.3 常量表达式函数的要求

将一个函数声明为constexpr,也是有几点要求的;当不满足这些要求,constexpr声明将会报编译错误。

  • 所有参数都必须是字面类型。

  • 返回类型必须是字面类型。

  • 函数体内必须有一个return。

  • return的表达式需要满足常量表达式的要求。

  • 构造返回值/表达式的任何构造函数或转换操作,都需要是constexpr。

看起来很简单,要在内联函数中使用到常量表达式,返回的还是个常量表达式,还不能对任何东西进行改动。constexpr函数就是无害的纯洁的函数。

constexpr类成员函数,需要追加几点要求:

  • constexpr成员函数不能是虚函数。

  • 对应类必须有字面类的成员。

constexpr构造函数的规则也有些不同:

  • 构造函数体必须为空。

  • 每一个基类必须可初始化。

  • 每个非静态数据成员都需要初始化。

  • 初始化列表的任何表达式,必须是常量表达式。

  • 构造函数可选择要进行初始化的数据成员,并且基类必须有constexpr构造函数。

  • 任何用于构建数据成员的构造函数和转换操作,以及和初始化表达式相关的基类必须为constexpr。

这些条件同样适用于成员函数,除非函数没有返回值,也就没有return语句。

另外,构造函数对初始化列表中的所有基类和数据成员进行初始化。一般的拷贝构造函数会隐式的声明为constexpr。

A.4.4 常量表达式和模板

将constexpr应用于函数模板,或一个类模板的成员函数;根据参数,如果模板的返回类型不是字面类,编译器会忽略其常量表达式的声明。当模板参数类型合适,且为一般inline函数,就可以将类型写成constexpr类型的函数模板。

  1. template<typename T>
  2. constexpr T sum(T a,T b)
  3. {
  4. return a+b;
  5. }
  6. constexpr int i=sum(3,42); // ok,sum<int>是constexpr
  7. std::string s=
  8. sum(std::string("hello"),
  9. std::string(" world")); // 也行,不过sum<std::string>就不是constexpr了

函数需要满足所有constexpr函数所需的条件。不能用多个constexpr来声明一个函数,因为其是一个模板;这样也会带来一些编译错误。


您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码: 验证码,看不清楚?请点击刷新验证码 必填



37 次浏览
5次