前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 ><<C++之旅>>阅读笔记

<<C++之旅>>阅读笔记

作者头像
高性能架构探索
发布2024-02-01 15:59:38
1200
发布2024-02-01 15:59:38
举报
文章被收录于专栏:技术随笔心得

你好,我是雨乐!

几周前,出版社赠了本C++之父新作<<C++之旅>>,因为当时比较忙,所以一直在手边放着,有时间的时候随意翻几页,断断续续也看了一部分,今天借助本文,分享下。

constexpr

constexpr的隐含意思是在编译阶段求值,对于一些求值操作,如果声明为constexpr,那么会编译器会尝试在编译阶段进行计算求值,如果求值成功,则用结果进行替换。

一个常用的例子是如下:

代码语言:javascript
复制
constexpr int factorial(int n) {
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

int main() {
  auto num = factorial(10); // 编译阶段求得值,可从汇编查看
  return 0;
}

如果一个变量声明为constexpr,那么意味着在编译阶段就要获得其值,比如如下这个例子:

代码语言:javascript
复制
#include <iostream>


constexpr int factorial(int n) {
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

int main() {
   int i = 10;
   constexpr auto num = factorial(i);
   return 0;
}

因为num被声明为constexpr,所以正如前面所说,在编译阶段求值,又因为i是一个非常量表达式,所以编译器报错如下:

代码语言:javascript
复制
<source>: In function 'int main()':
<source>:10:35: error: the value of 'i' is not usable in a constant expression
   10 |    constexpr auto num = factorial(i);
      |                                   ^
<source>:9:8: note: 'int i' is not const
    9 |    int i = 10;
      |        ^
Compiler returned: 1

if语句初始化

这个是自C++17才支持的,可以在if语句中进行初始化,随后进行条件判断,如下:

代码语言:javascript
复制
if (int a = 0; a != 10);

也可以像下面这样:

代码语言:javascript
复制
std::vector<int> v;
// operation
if (auto size = v.size()); 

引用赋值不会改变其初始指向

示例:

代码语言:javascript
复制
int x = 2, y = 3;
int &r1 = x;
int &r2 = y;
r1 = r2;
std::cout << x  << " " << y << " " <<  r1 << " " << r2;

在上述代码中,虽然最后r1被赋值为r2,但是其仍然指向x,这样的结果就是x的值也被修改了。最后输出结果为3 3 3 3

enum vs enum类

传统的enum,如果像下面这样定义:

代码语言:javascript
复制
enum Colors {
    Red,
    Green,
    Blue
  };
  
  enum OtherColors {
    Yellow,
    Blue
  };

编译器会报如下错误:

代码语言:javascript
复制
<source>:16:5: error: 'Blue' conflicts with a previous declaration
   16 |     Blue
      |     ^~~~
<source>:11:5: note: previous declaration 'Colors Blue'
   11 |     Blue
      |     ^~~~

为了解决这个问题,引入了enum class,如下:

代码语言:javascript
复制
enum class Colors {
    Red,
    Green,
    Blue
  };
  
  enum class OtherColors {
    Yellow,
    Blue
  };

modules

示例如下:

// Vector.h:

代码语言:javascript
复制
class Vector {
 public:
  Vector(int s);
  double& operator[](int i);
  int size();
 private:
  double∗ elem; // elem points to an array of sz doubles
  int sz;
};

// Vector.cpp:

代码语言:javascript
复制
#include "Vector.h" // get Vector’s interface
Vector::Vector(int s) :elem{new double[s]}, sz{s} {
}
double& Vector::operator[](int i) {
  return elem[i];
}
int Vector::size() {
  return sz;
}

// user.cpp

代码语言:javascript
复制
#include "Vector.h" // get Vector’s interface
#include <cmath> // get the standard-librar y math function interface including sqrt()
double sqrt_sum(Vector& v) {
  double sum = 0;
  for (int i=0; i!=v.siz e(); ++i)
  sum+=std::sqr t(v[i]); // sum of square roots
  return sum;
}

最终格式如下:

可以单独对user.cpp 和 Vector.cpp编译,生成.o文件,这是因为上述示例使用了#include操作,预处理器在遇到#include的时候,会将其中的内容完整的拷贝一份到相应的文件,这就导致每个.cpp都有头文件Vector.h的一个副本,代码体积膨胀不说,还增加了编译时间。

为了解决上述问题,引入了modules,使用module优化上述代码,如下:

// Vector.cpp

代码语言:javascript
复制
module;
export module Vector; // defining the module called "Vector"
export class Vector {
 public:
  Vector(int s);
  double& operator[](int i);
  int size();
 private:
  double∗ elem; // elem points to an array of sz doubles
  int sz;
};

Vector::Vector(int s) :elem{new double[s]}, sz{s} {
}
double& Vector::operator[](int i) {
return elem[i];
}
int Vector::siz e() {
return sz;
}
expor t int size(const Vector& v) { return v.siz e(); }

// user.cpp

代码语言:javascript
复制
import Vector; // get Vector’s interface
#include <cmath> // get the standard-librar y math function interface including sqrt()
double sqrt_sum(Vector& v) {
  double sum = 0;
  for (int i=0; i!=v.siz e(); ++i)
  sum+=std::sqr t(v[i]); // sum of square roots
  return sum;
}

对于这块的内容,可以详细阅读之前的文章:

未来已来:C++ modules初探

纯虚函数

如果其中一个成员函数使用= 0,那么该函数为纯虚函数,继承于存在纯虚函数的子类,其必须实现该函数:

代码语言:javascript
复制
class Base {
 public:
  void fun() = 0;
};

class Derived : public Base {
 public:
  void fun() {}
}

如果声明一个存在纯虚函数的类,那边编译器会报错如下:

代码语言:javascript
复制
<source>:21:8: error: initializer specified for non-virtual method 'void Base::fun()'
   21 |   void fun() = 0;
      |        ^~~

派生

判断一个类是否继承于另外一个类,可以使用如下方式:

代码语言:javascript
复制
template<typename Base, typename T>
inline bool IsDerivedOf(T *ptr) {
  return dynamic_cast<Base*>(ptr) != nullptr;
}

智能指针

智能指针可以避免内存泄漏,此处文章较多,可以参考:

智能指针-使用、避坑和实现

一次诡异的内存泄漏

显式构造

对于如下这种:

代码语言:javascript
复制
class Vector {
 public:
  Vector(int sz);
}

可以使用Vector v = 3;这种方式进行初始化,往往这种并不是我们希望看到的,所以可以使用关键字explicit来强制显式初始化:

代码语言:javascript
复制
class Vector {
 public:
 explicit Vector(int sz);
}

move语义

自C++11起引入了move语义,不过这个很容易引起初学者的误解,其实move()本身并不做任何操作,其只是进行了简单的类型转换,而真正的移动操作需要类实现者进行定义。

STL对其定义如下:

代码语言:javascript
复制
template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

如果需要深入了解其特性,可以参考文章:

【Modern C++】深入理解移动语义

CTAD

CTAD为Class Template Argument Deduction的缩写,中文称为类模板参数推导,在C++17之前,我们需要像下面这样写:

代码语言:javascript
复制
std::pair<int, double> p1 = {1, 3.41};

现在我们可以如下这样写:

代码语言:javascript
复制
std::pair              p2 = {1, 3.41};

可以参考文章【ModernCpp】新特性之CTAD

字面量

如果我们像下面这样写:

代码语言:javascript
复制
auto s = "hello";

s会被编译器推导为const char*,为了使得其为std::string类型,有以下几种方式:

代码语言:javascript
复制
auto s1 = std::string("hello");
std::string s2 = "hello";
auto s3 = "hello"s;

前两种方式比较常见,第三种方式是Modern cpp的新特性,俗称字面量。在这个语法中,"hello"是字符串字面值,而"s"是用户定义字面量的后缀,它将字符串字面值转换为std::string对象。

COW VS SSO

COW,想必大家都清楚其原理,这个机制很常用,比较常见的如fork等操作,在STL中也有用到这个,比如gcc5.1之前的string中,先看如下代码:

代码语言:javascript
复制
std::string s("str");
 std::string s1 = s;
 char *p = const_cast<char*>(s1.data());
 p[2] = '\0';
 
 std::cout << s << std::endl;
 std::cout << s1 << std::endl;

输出结果无非以下两种:

代码语言:javascript
复制
st
st

或者

代码语言:javascript
复制
str
st

第一种基于gcc5.1前的版本编译,第二种输出基于5.1之后的版本编译,这两个输出的不同正是源于gcc5.1之前的版本对于string的复制采用了COW操作。

自gcc5.1之后,字符串优化采用了新的机制,即SSO,其为Small String Optimization的简写,中文译为小字符串优化,基本原理是:当分配大小小于16个字节时候,从栈上进行分配,而如果大于等于16个字节,则在堆上进行内存分配

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能架构探索 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • constexpr
  • if语句初始化
  • 引用赋值不会改变其初始指向
  • enum vs enum类
  • modules
  • 纯虚函数
  • 派生
  • 智能指针
  • 显式构造
  • move语义
  • CTAD
  • 字面量
  • COW VS SSO
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档