十二、 C++特性之 杂合

static_assert和 type traits

static_assert提供一个编译时的断言检查。如果断言为真,什么也不会发生。如果断言为假,编译器会打印一个特殊的错误信息。

  1. template <typename T, size_t Size>
  2. class Vector
  3. {
  4. static_assert(Size < 3, "Size is too small");
  5. T _points[Size];
  6. };
  7. int main()
  8. {
  9. Vector<int, 16> a1;
  10. Vector<double, 2> a2;
  11. return 0;
  12. }
  1. error C2338: Size is too small
  2. see reference to class template instantiation ‘Vector<T,Size>‘ being compiled
  3. with
  4. [
  5. T=double,
  6. Size=2
  7. ]

static_assert和type traits一起使用能发挥更大的威力。type traits是一些class,在编译时提供关于类型的信息。在头文件<type_traits>中可以找到它们。这个头文件中有好几种 class: helper class,用来产生编译时常量。type traits class,用来在编译时获取类型信息,还有就是type transformation class,他们可以将已存在的类型变换为新的类型。

下面这段代码原本期望只做用于整数类型。

  1. template <typename T1, typename T2>
  2. auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
  3. {
  4. return t1 + t2;
  5. }

但是如果有人写出如下代码,编译器并不会报错

  1. std::cout << add(1, 3.14) << std::endl;
  2. std::cout << add("one", 2) << std::endl;

程序会打印出4.14和”e”。但是如果我们加上编译时断言,那么以上两行将产生编译错误。

  1. template <typename T1, typename T2>
  2. auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
  3. {
  4. static_assert(std::is_integral<T1>::value, "Type T1 must be integral");
  5. static_assert(std::is_integral<T2>::value, "Type T2 must be integral");
  6. return t1 + t2;
  7. }
  1. error C2338: Type T2 must be integral
  2. see reference to function template instantiation ‘T2 add<int,double>(T1,T2)‘ being compiled
  3. with
  4. [
  5. T2=double,
  6. T1=int
  7. ]
  8. error C2338: Type T1 must be integral
  9. see reference to function template instantiation ‘T1 add<const char*,int>(T1,T2)‘ being compiled
  10. with
  11. [
  12. T1=const char *,
  13. T2=int
  14. ]

Move semantics (Move语义)

这是C++11中所涵盖的另一个重要话题。就这个话题可以写出一系列文章,仅用一个段落来说明显然是不够的。因此在这里我不会过多的深入细节,如果你还不是很熟悉这个话题,我鼓励你去阅读更多地资料。

C++11加入了右值引用(rvalue reference)的概念(用&&标识),用来区分对左值和右值的引用。左值就是一个有名字的对象,而右值则是一个无名对象(临时对 象)。move语义允许修改右值(以前右值被看作是不可修改的,等同于const T&类型)。

C++的class或者struct以前都有一些隐含的成员函数:默认构造函数(仅当没有显示定义任何其他构造函数时才存在),拷贝构造函数,析构 函数还有拷贝赋值操作符。拷贝构造函数和拷贝赋值操作符提供bit-wise的拷贝(浅拷贝),也就是逐个bit拷贝对象。也就是说,如果你有一个类包含 指向其他对象的指针,拷贝时只会拷贝指针的值而不会管指向的对象。在某些情况下这种做法是没问题的,但在很多情况下,实际上你需要的是深拷贝,也就是说你 希望拷贝指针所指向的对象。而不是拷贝指针的值。这种情况下,你需要显示地提供拷贝构造函数与拷贝赋值操作符来进行深拷贝。

如果你用来初始化或拷贝的源对象是个右值(临时对象)会怎么样呢?你仍然需要拷贝它的值,但随后很快右值就会被释放。这意味着产生了额外的操作开销,包括原本并不需要的空间分配以及内存拷贝。

现在说说move constructor和move assignment operator。这两个函数接收T&&类型的参数,也就是一个右值。在这种情况下,它们可以修改右值对象,例如“偷走”它们内部指针所 指向的对象。举个例子,一个容器的实现(例如vector或者queue)可能包含一个指向元素数组的指针。当用一个临时对象初始化一个对象时,我们不需 要分配另一个数组,从临时对象中把值复制过来,然后在临时对象析构时释放它的内存。我们只需要将指向数组内存的指针值复制过来,由此节约了一次内存分配, 一次元数组的复制以及后来的内存释放。

以下代码实现了一个简易的buffer。这个buffer有一个成员记录buffer名称(为了便于以下的说明),一个指针(封装在unique_ptr中)指向元素为T类型的数组,还有一个记录数组长度的变量。

  1. template <typename T>
  2. class Buffer
  3. {
  4. std::string          _name;
  5. size_t               _size;
  6. std::unique_ptr<T[]> _buffer;
  7. public:
  8. // default constructor
  9. Buffer():
  10. _size(16),
  11. _buffer(new T[16])
  12. {}
  13. // constructor
  14. Buffer(const std::string& name, size_t size):
  15. _name(name),
  16. _size(size),
  17. _buffer(new T[size])
  18. {}
  19. // copy constructor
  20. Buffer(const Buffer& copy):
  21. _name(copy._name),
  22. _size(copy._size),
  23. _buffer(new T[copy._size])
  24. {
  25. T* source = copy._buffer.get();
  26. T* dest = _buffer.get();
  27. std::copy(source, source + copy._size, dest);
  28. }
  29. // copy assignment operator
  30. Buffer& operator=(const Buffer& copy)
  31. {
  32. if(this != ©)
  33. {
  34. _name = copy._name;
  35. if(_size != copy._size)
  36. {
  37. _buffer = nullptr;
  38. _size = copy._size;
  39. _buffer = _size > 0 > new T[_size] : nullptr;
  40. }
  41. T* source = copy._buffer.get();
  42. T* dest = _buffer.get();
  43. std::copy(source, source + copy._size, dest);
  44. }
  45. return *this;
  46. }
  47. // move constructor
  48. Buffer(Buffer&& temp):
  49. _name(std::move(temp._name)),
  50. _size(temp._size),
  51. _buffer(std::move(temp._buffer))
  52. {
  53. temp._buffer = nullptr;
  54. temp._size = 0;
  55. }
  56. // move assignment operator
  57. Buffer& operator=(Buffer&& temp)
  58. {
  59. assert(this != &temp); // assert if this is not a temporary
  60. _buffer = nullptr;
  61. _size = temp._size;
  62. _buffer = std::move(temp._buffer);
  63. _name = std::move(temp._name);
  64. temp._buffer = nullptr;
  65. temp._size = 0;
  66. return *this;
  67. }
  68. };
  69. template <typename T>
  70. Buffer<T> getBuffer(const std::string& name)
  71. {
  72. Buffer<T> b(name, 128);
  73. return b;
  74. }
  75. int main()
  76. {
  77. Buffer<int> b1;
  78. Buffer<int> b2("buf2", 64);
  79. Buffer<int> b3 = b2;
  80. Buffer<int> b4 = getBuffer<int>("buf4");
  81. b1 = getBuffer<int>("buf5");
  82. return 0;
  83. }

默认的copy constructor以及copy assignment operator大家应该很熟悉了。C++11中新增的是move constructor以及move assignment operator,这两个函数根据上文所描述的move语义实现。如果你运行这段代码,你就会发现b4构造时,move constructor会被调用。同样,对b1赋值时,move assignment operator会被调用。原因就在于getBuffer()的返回值是一个临时对象——也就是右值。

你也许注意到了,move constuctor中当我们初始化变量name和指向buffer的指针时,我们使用了std::move。name实际上是一个 string,std::string实现了move语义。std::unique_ptr也一样。但是如果我们写_name(temp._name), 那么copy constructor将会被调用。不过对于_buffer来说不能这么写,因为std::unique_ptr没有copy constructor。但为什么std::string的move constructor此时没有被调到呢?这是因为虽然我们使用一个右值调用了Buffer的move constructor,但在这个构造函数内,它实际上是个左值。为什么?因为它是有名字的——“temp”。一个有名字的对象就是左值。为了再把它变为 右值(以便调用move constructor)必须使用std::move。这个函数仅仅是把一个左值引用变为一个右值引用。

更新:虽然这个例子是为了说明如何实现move constructor以及move assignment operator,但具体的实现方式并不是唯一的。在本文的回复中Member 7805758同学提供了另一种可能的实现。为了方便查看,我把它也列在下面:

  1. template <typename T>
  2. class Buffer
  3. {
  4. std::string          _name;
  5. size_t               _size;
  6. std::unique_ptr<T[]> _buffer;
  7. public:
  8. // constructor
  9. Buffer(const std::string& name = "", size_t size = 16):
  10. _name(name),
  11. _size(size),
  12. _buffer(size? new T[size] : nullptr)
  13. {}
  14. // copy constructor
  15. Buffer(const Buffer& copy):
  16. _name(copy._name),
  17. _size(copy._size),
  18. _buffer(copy._size? new T[copy._size] : nullptr)
  19. {
  20. T* source = copy._buffer.get();
  21. T* dest = _buffer.get();
  22. std::copy(source, source + copy._size, dest);
  23. }
  24. // copy assignment operator
  25. Buffer& operator=(Buffer copy)
  26. {
  27. swap(*this, copy);
  28. return *this;
  29. }
  30. // move constructor
  31. Buffer(Buffer&& temp):Buffer()
  32. {
  33. swap(*this, temp);
  34. }
  35. friend void swap(Buffer& first, Buffer& second) noexcept
  36. {
  37. using std::swap;
  38. swap(first._name  , second._name);
  39. swap(first._size  , second._size);
  40. swap(first._buffer, second._buffer);
  41. }
  42. };

结论

关于C++11还有很多要说的。本文只是各种入门介绍中的一个。本文展示了一系列C++开发者应当使用的核心语言特性与标准库函数。然而我建议你能更加深入地学习,至少也要再看看本文所介绍的特性中的部分。

Deleted和Defaulted函数

一个表单中的函数:

  1. struct A
  2. {
  3. A()=default; //C++11
  4. virtual ~A()=default; //C++11
  5. };

被称为一个defaulted函数,“=default;”告诉编译器为函数生成默认的实现。Defaulted函数有两个好处:比手工实现更高效,让程序员摆脱了手工定义这些函数的苦差事。

与defaulted函数相反的是deleted函数:

  1. int func()=delete;

Deleted函数对防止对象复制很有用,回想一下C++自动为类声明一个副本构造函数和一个赋值操作符,要禁用复制,声明这两个特殊的成员函数=delete即可:

  1. struct NoCopy
  2. {
  3. NoCopy & operator =( const NoCopy & ) = delete;
  4. NoCopy ( const NoCopy & ) = delete;
  5. };
  6. NoCopy a;
  7. NoCopy b(a); //compilation error, copy ctor is deleted

委托构造函数

在C++11中,构造函数可以调用相同类中的其它构造函数:

  1. class M //C++11 delegating constructors
  2. {
  3. int x, y;
  4. char *p;
  5. public:
  6. M(int v) : x(v), y(0),  p(new char [MAX])  {} //#1 target
  7. M(): M(0) {cout<<"delegating ctor"<

构造函数#2,委托构造函数,调用目标构造函数#1。

线程库

站在程序员的角度来看,C++11最重要的新功能毫无疑问是并行操作,C++11拥有一个代表执行线程的线程类,在并行环境中用于同步,async()函数模板启动并行任务,为线程独特的数据声明thread_local存储类型。如果你想找C++11线程库的快速教程,请阅读Anthony William的“C++0x中更简单的多线程”。

新的算法

C++11标准库定义了新的算法模仿all_of(),any_of()和none_of()操作,下面列出适用于ispositive()到(first, first+n)范围,且使用all_of(), any_of() and none_of() 检查范围的属性的谓词:

  1. #include <algorithm>
  2. //C++11 code
  3. //are all of the elements positive?
  4. all_of(first, first+n, ispositive()); //false
  5. //is there at least one positive element?
  6. any_of(first, first+n, ispositive());//true
  7. // are none of the elements positive?
  8. none_of(first, first+n, ispositive()); //false

一种新型copy_n算法也可用了,使用copy_n()函数,复制一个包含5个元素的数组到另一个数组的代码如下:

  1. #include
  2. int source[5]={0,12,34,50,80};
  3. int target[5];
  4. //copy 5 elements from source to target
  5. copy_n(source,5,target);

算法iota()创建了一个值顺序递增的范围,好像分配一个初始值给*first,然后使用前缀++使值递增,在下面的代码中,iota()分配连续值{10,11,12,13,14}给数组arr,并将{‘a’,’b’,’c’}分配给char数组c。

  1. include <numeric>
  2. int a[5]={0};
  3. char c[3]={0};
  4. iota(a, a+5, 10); //changes a to {10,11,12,13,14}
  5. iota(c, c+3, ‘a‘); //{‘a‘,‘b‘,‘c‘}

C++11仍然缺乏一些有用的库,如XML API,套接字,GUI,反射以及前面提到的一个合适的自动垃圾回收器,但C++11的确也带来了许多新特性,让C++变得更加安全,高效,易学易用。

如果C++11的变化对你来说太大的话,也不要惊慌,多花些时间逐渐消化这一切,当你完全吸收了C++11的变化后,你可能就会同意Stroustrup的说法:C++11感觉就像一个新语言,一个更好的新语言。

变长参数的模板

我们在C++中都用过pair,pair可以使用make_pair构造,构造一个包含两种不同类型的数据的容器。比如,如下代码:

auto p = make_pair(1, "C++ 11");

由于在C++11中引入了变长参数模板,所以发明了新的数据类型:tuple,tuple是一个N元组,可以传入1个, 2个甚至多个不同类型的数据

auto t1 = make_tuple(1, 2.0, "C++ 11");
auto t2 = make_tuple(1, 2.0, "C++ 11", {1, 0, 2});

这样就避免了从前的pair中嵌套pair的丑陋做法,使得代码更加整洁

另一个经常见到的例子是Print函数,在C语言中printf可以传入多个参数,在C++11中,我们可以用变长参数模板实现更简洁的Print

template<typename head, typename... tail>
void Print(Head head, typename... tail) {
    cout<< head <<endl;
    Print(tail...);
}
时间: 2024-08-24 03:39:33

十二、 C++特性之 杂合的相关文章

PHP语言的十二种特性

命名约定前后矛盾.版本兼容相互冲突,PHP语言在各个层面上都给开发人员带来了诡异而难解的谜局--这就是我们今天要讨论的话题.我们真的对PHP深恶痛绝吗?不,当然不是.如果真是这样,我们就不会如此广泛地使用Drupal.WordPress以及其它一些同类框架.假设我们真的讨厌PHP,那么最直接的办法是转向Java而非在这里放什么厥词.不过需要提醒各位拥护者的是,也别因为熟悉而对PHP的那些缺陷视而不见. 转换令人头痛 在利用PHP进行项目创建的过程中,最大的挑战之一在于牢记我们哪些时候需要输入HT

Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验

Android群英传笔记--第十二章:Android5.X 新特性详解,Material Design UI的新体验 第十一章为什么不写,因为我很早之前就已经写过了,有需要的可以去看 Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 这一章很多,但是很有趣,也是这书的最后一章知识点了,我现在还在考虑要不要写这个拼图和2048的案例,在此之前,我们先来玩玩Android5.X的新特性吧!

【OpenCV入门教程之十二】OpenCV边缘检测:Canny算子,Sobel算子,Laplace算子,Scharr滤波器合辑

本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/25560901 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 知乎:http://www.zhihu.com/people/mao-xing-yun 邮箱: [email protected] 写作当前博文时配套使用的OpenCV版本: 2.4.9 本篇文章中,我们将一起学习OpenCV中

(十二)boost库之多线程高级特性

(十二)boost库之多线程高级特性 很多时候,线程不仅仅是执行一些耗时操作,可能我们还需要得到线程的返回值,一般的处理方法就是定义一个全局状态变量,不断轮训状态,就如我目前维护的一个项目,全局变量定义了N中状态,看的让人抓狂.该项目的大体逻辑是这样的,启动K个线程,当线程执行到某一个点时,进行轮训,判断是否所有线程都执行到该点,单独开启了一个线程用于轮训所有线程是否结束,待所有线程结束后会获取数据,生成一个文件,另外还有一个线程就在轮训文件是否生成,然后读取文件进行下一步操作.各种的轮训,显得

一、数据库表中字段的增删改查,二、路由基础.三、有名无名分组.四、多app共存的路由分配.五、多app共存时模板冲突问题.六、创建app流程.七、路由分发.八、路由别名,九、名称空间.十、反向解析.十一、2.x新特性.十二、自定义转换器

一.数据库表中字段的增删改查 ''' 直接在modules中对字段进行增删改查 然后在tools下点击Run manage.py Task执行makemigrations和migrate 注意在执行字段的删除过程中需不需要对数据进行备份 ''' 二.路由基础 ''' # url中含有四个参数 # url(regex, view, kwargs=None, name=None) # 正则路径 视图函数地址 默认关键字参数(了解) 路由别名 # r'index' 只要请求中含有index都可以匹配成

【老孙点评】古人读书十二法

书,人人都可以去读,但是有的人就读不懂.读不通.读不进,甚至越读越糊涂.这里说明读书是有得法与不得法的区别的,但要相信方法总是可寻的.读书不得法,就如上面所说的那样,反之,也有不少人把书读懂而且读通了.读书的方法,也不止一种,现在选列了古人读书十二法,以供借鉴与参考: [法一]."思·问·习"读书法.这是孔子主张的读书方法. [例]1.重视思考.在学习过程中,要动脑筋.他说:"学而不思则罔,思而不学则殆."(<论语·为政>) [例]2.不懂就问.读书在于

JAVA之旅(三十)——打印流PrintWriter,合并流,切割文件并且合并,对象的序列化Serializable,管道流,RandomAccessFile,IO其他类,字符编码

JAVA之旅(三十)--打印流PrintWriter,合并流,切割文件并且合并,对象的序列化Serializable,管道流,RandomAccessFile,IO其他类,字符编码 三十篇了,又是一个阳光明媚的周末,一个又一个的周末,周而复始,不断学习,前方的路你可曾看见?随我一起走进技术的世界,流连忘返吧! 一.打印流PrintWriter 打印流有PrintWriter和PrintStream,他的特点可以直接操作输入流还有文件 该流提供了打印方法,可以将各种数据类型原样打印 file对象

信息安全系统设计基础第十二周学习总结

第十二周代码学习 一.environ.c #include <stdio.h> #include <stdlib.h> int main(void) { printf("PATH=%s\n", getenv("PATH")); setenv("PATH", "hello", 1); printf("PATH=%s\n", getenv("PATH")); #if

《Programming in Lua 3》读书笔记(二十二)

日期:2014.8.6 PartⅣ The C API 26 Extending Your Application 使用Lua很重要的一点是用来做配置语言.配合主语言做一些功能的配置. 26.1 The Basics 有的时候程序需要配置一些功能信息,很多时候可能有许多别的方法比用lua做配置要更简单:如使用环境变量或者读取文件,读取文件涉及到文件的解析.如果使用Lua进行配置的话,相当于用lua文件替代了要读取的如csv.txt文件等. 使用Lua进行配置的时候,就需要使用Lua API去控制