Online reading link: https://changkun.de/modern-cpp/zh-cn/00-preface/
I am currently reading the "Modern C++ Tutorial" on the link, and post it as a study note. If there is any infringement, it will be deleted immediately .
Chapter 1 Towards Modern C++
"Modern C++" vs. "Traditional C++"
C++ has experienced three major standards of C++98/11/20 , interspersed with C++14/17's supplements and optimizations to the previous standards. This book refers to C++11 and its later C++ features as **"modern C++" , and C++98 and its previous C++ features as "traditional C++"**.
Modern C++, on the one hand, greatly enhances the usability of the language. The auto keyword is convenient for users to manipulate more complex template types. Lambda expressions make C++ have the "closure" feature of "anonymous functions". Rvalue references solve the long-term problem of C++. The efficiency of temporary objects that has been criticized. On the other hand, it has added a lot of tools and methods to its own standard library, such as std::thread
supporting concurrent programming independently of the underlying system , std::regex
providing complete regular expression support, and so on.
C:gcc,C++:g++
C-style casts are deprecated (i.e. used before variables (convert_type)
), and static_cast
, reinterpret_cast
, , const_cast
etc. should be used instead.
C++ is not a superset of C, there is a difference between C++98 and C99.
Chapter 2 Enhancing Language Usability
The emergence of new features is to solve and optimize some problems in traditional C++, making it more convenient to use. In terms of the usability of enhanced language , there are six constants, variables and their initialization, type deduction, control flow, templates and object-oriented improvements.
Constants : ① Introduce nullptr
keywords to distinguish null pointers and 0, which can be implicitly converted to any pointer or member pointer, and perform equal or unequal comparisons; ② Introduce keywords constexpr
to modify const constants and functions, so that Put directly where a constant expression is expected (such as a quantity when creating an array).
Variables and their initialization : ① The creation of temporary variables can be put into if/switch
the statement to avoid the temporary variables occupying memory and names only once. ② Introduced std::initializer_list
to allow constructors or other functions to use initialized linked lists like parameters. ③ The introduction of tuples std::tuple
implements multiple return values, and auto [x,y,..]
uses them to automatically obtain content.
Type deduction : Use auto
and decltype
to deduce the type of a variable or expression, and provide a comparison of whether it is the same type, and further use it in the return value of the function.
Control flow : Some optimizations for conditional statements and loop statements, such as ① introducing constexpr
keyword features to make the code complete branch judgment at compile time, ② using auto
automatic traversal of container intervals.
Template : ① explicitly inform the compiler when to instantiate the template; ② do not >>
directly use continuous right angle brackets as the right shift operator, but consider the case of nested template classes; ③ introduce using
the definition of template alias; ④ Introduce ...
as variable-length template parameters, allowing any number and type of template parameters; ⑤ Use folding expressions to simplify the calculation of variable-length template parameters; ⑥ Use the automatic deduction of auto
data types between <>.
Object -oriented : ① delegate construction, call another constructor in the same class constructor, so as to achieve the purpose of simplifying the code; ② use using
inheritance constructor; ③ use override
and final
two keywords to solve virtual function overloading and class inheritance Some problems; ④ allows to explicitly declare to adopt or reject the compiler's own constructor. ⑤ The enumeration class (enumeration class) is introduced to ensure the type safety of the enumeration.
2.1 Constants
nullptr
C++11 introduced nullptr
the keyword , which is specially used to distinguish between null pointers and 0. nullptr
is of type that nullptr_t
can be implicitly converted to, and compared for equality or inequality with, any pointer or pointer to member. It is recommended to develop the habit nullptr
of .
decltype
for type deduction :decltype(NULL)
std::is_same
Used to determine whether two types are the same :std::is_same<decltype(NULL), std::nullptr_t>::value
constexpr
When creating an array, the C++ standard requires that the length of the array must be a constant expression . When char arr_4[xx]
in xx
contains const
a constant or a function, the expression for creating an array is illegal. You can use constexpr
the feature to solve this problem.
2.2 Variables and their initialization
if/switch variable declaration enhancement
Temporary variables can be placed inside an if statement.
if (const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(), 3);
itr != vec.end()) {
*itr = 4;
}
Initialize linked list
Binding changes to the initializer list to the type, called it std::initializer_list
, allows constructors or other functions to use the initializer list as arguments:
#include <initializer_list> // 需要引用对应头文件
#include <vector>
#include <iostream>
class MagicFoo {
public:
std::vector<int> vec;
MagicFoo(std::initializer_list<int> list) {
for (std::initializer_list<int>::iterator it = list.begin();
it != list.end(); ++it)
vec.push_back(*it);
}
void foo(std::initializer_list<int> list) {
for (std::initializer_list<int>::iterator it = list.begin();
it != list.end(); ++it) vec.push_back(*it);
}
};
int main() {
// after C++11
MagicFoo magicFoo = {
1, 2, 3, 4, 5};
magicFoo.foo({
6,7,8,9});
Foo foo2 {
3, 4};
}
structured binding
For situations that require multiple return values , C++11 provides std::tuple
a container for constructing a tuple , while C++11/14 does not provide an easy way to get it directly from the tuple and define the tuple elements, and it is necessary to clearly know how many objects the tuple contains and what types they are. Until this setting is perfected in C++17:
#include <iostream>
#include <tuple>
std::tuple<int, double, std::string> f() {
return std::make_tuple(1, 2.3, "456");
}
int main() {
auto [x, y, z] = f();
std::cout << x << ", " << y << ", " << z << std::endl;
return 0;
}
2.3 Type deduction
auto、decltype
auto
decltype
The two keywords implement type deduction and let the compiler worry about the variable type:
-
auto
: Type deduction for variables . Starting from C++20,auto
it can even be used to pass parameters to functions, but it cannot be used to deduce array types at present. -
decltype
: The type used to evaluate an expression .auto x = 1; auto y = 2; decltype(x+y) z;
-
Determine whether it is the same type :
if (std::is_same<decltype(x), int>::value) std::cout << "type x == int" << std::endl; if (std::is_same<decltype(x), float>::value) std::cout << "type x == float" << std::endl; if (std::is_same<decltype(x), decltype(z)>::value) std::cout << "type z == type x" << std::endl;
tail return type deduction
C++11, tail return type, need to add type deduction at the end, you can use std::is_same
to check whether the type deduction is correct
template<typename T, typename U>
auto add2(T x, U y) -> decltype(x+y){
return x + y;
}
C++14 allows ordinary functions to directly have return value deduction. Note that the return value of list initialization cannot be used together with auto.
template<typename T, typename U>
auto add3(T x, U y){
return x + y;
}
decltype(auto) parameter forwarding
std::string lookup1();
decltype(auto) look_up_a_string_1() {
return lookup1();
}
2.4 Control flow
if constexpr
Introduce the constexpr keyword feature into the conditional judgment , so that the code can complete the branch judgment at compile time, such as:
#include <iostream>
template<typename T>
auto print_type_info(const T& t) {
if constexpr (std::is_integral<T>::value) {
return t + 1;
} else {
return t + 0.001;
}
}
int main() {
std::cout << print_type_info(5) << std::endl;
std::cout << print_type_info(3.14) << std::endl;
}
When compiled it behaves as:
int print_type_info(const int& t) {
return t + 1;
}
double print_type_info(const double& t) {
return t + 0.001;
}
int main() {
std::cout << print_type_info(5) << std::endl;
std::cout << print_type_info(3.14) << std::endl;
}
Interval for iteration
Write a loop statement as concise as python
for (auto element : vec)
std::cout << element << std::endl; // read only
for (auto &element : vec) {
element += 1; // writeable
}
2.5 Templates
The philosophy of templates is to throw some problems that can be handled at compile time into compile time, and only handle the core dynamic services at runtime.
【External template】
The original syntax to force the compiler to instantiate a template at a specific location has been extended to explicitly tell the compiler when to instantiate a template:
template class std::vector<bool>; // 强行实例化
extern template class std::vector<double>; // 不在该当前编译文件中实例化模板
angle brackets ">"
In the traditional C++ compiler, >>
it is always treated as a right shift operator, but it is easy to write in the code of the nested template (as follows), so starting from C++11, continuous right angle brackets become legal.
std::vector<std::vector<int>> matrix;
type alias template
In traditional C++, you can use typedef
to define a new name for a type, but there is no way to define a new name for a template. In C++11 using
, the following form is used to support the definition of template aliases
template<typename T, typename U>
class MagicType {
public:
T dark;
U magic;
};
template<typename T>
using TrueDarkMagic = MagicType<std::vector<T>, std::string>;
int main() {
TrueDarkMagic<bool> you;
}
【Variable length parameter template】
C++11 adds a new representation method, which allows any number and type of template parameters, and uses ...
to represent variable-length template parameters. The same method can also be used for function parameters to represent variable-length parameters, or at least one template parameter can be manually defined
template<typename... Ts> class Magic;
class Magic<int,
std::vector<int>,
std::map<std::string,
std::vector<int>>> darkMagic;
// 手动定义至少一个模板参数
template<typename Require, typename... Args> class Magic;
// 定义变长函数
template<typename... Args> void printf(const std::string &str, Args... args);
// 解包
template<typename... Ts>
void magic(Ts... args) {
std::cout << sizeof...(args) << std::endl;
}
When unpacking, first use sizeof...
to calculate the number of parameters, and then there are several classic processing methods: ① recursive template; ② (C++17) variable parameter template expansion; ③ initialization list expansion: using initialization lists and Lambda expressions characteristics.
/*1.递归模板----------------------------------------------------*/
#include <iostream>
template<typename T0>
void printf1(T0 value) {
std::cout << value << std::endl;
}
template<typename T, typename... Ts>
void printf1(T value, Ts... args) {
std::cout << value << std::endl;
printf1(args...);
}
int main() {
printf1(1, 2, "123", 1.1);
return 0;
}
/*2.变参模板展开----------------------------------------------------*/
template<typename T0, typename... T>
void printf2(T0 t0, T... t) {
std::cout << t0 << std::endl;
if constexpr (sizeof...(t) > 0) printf2(t...);
}
/*3.初始化列表展开----------------------------------------------------*/
template<typename T, typename... Ts>
auto printf3(T value, Ts... args) {
std::cout << value << std::endl;
(void) std::initializer_list<T>{
([&args] {
std::cout << args << std::endl;
}(), value)...};
}
【Fold expression】
#include <iostream>
template<typename ... T>
auto sum(T ... t) {
return (t + ...);
}
int main() {
std::cout << sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << std::endl;
}
Non-type template argument deduction
Types of data between using auto
auto deduction<>
template <auto value> void foo() {
std::cout << value << std::endl;
return;
}
int main() {
foo<10>(); // value 被推导为 int 类型
}
2.6 Object-oriented
delegate constructor
One constructor in the same class calls another constructor, so as to achieve the purpose of simplifying the code, such as:
#include <iostream>
class Base {
public:
int value1;
int value2;
Base() {
value1 = 1;
}
Base(int value) : Base() {
// 委托 Base() 构造函数
value2 = value;
}
};
int main() {
Base b(2);
std::cout << b.value1 << std::endl;
std::cout << b.value2 << std::endl;
}
inheritance construct
using
Introducing the concept of inherited constructors using
#include <iostream>
class Base {
public:
int value1;
int value2;
Base() {
value1 = 1;
}
Base(int value) : Base() {
// 委托 Base() 构造函数
value2 = value;
}
};
class Subclass : public Base {
public:
using Base::Base; // 继承构造
};
int main() {
Subclass s(3);
std::cout << s.value1 << std::endl;
std::cout << s.value2 << std::endl;
}
explicit function overloading
First introduce the concept and function of virtual functions : when a pointer to a base class operates its polymorphic class object, it will call its corresponding function according to different class objects. This function is a virtual function. In the base class, after using to virtual
define a virtual function, the function of the same name in the default subclass is also a virtual function, no matter whether it is declared again or not virtual
. Two exceptions may occur:
- Programmers don't want to try to overload functions , but just happen to include a function with the same name. (? If the names and parameter types are exactly the same, no error will be reported, and it will be difficult to find the problem)
- The programmer wants to overload a certain function , but the virtual function in the base class has been deleted, and the function added to the subclass at this time becomes an ordinary class method.
To this end, C++11 introduces override
and final
two keywords:
override
: Explicitly tell the compiler to overload, the compiler will check whether there is such a virtual function in the base class, if not, it will fail to compile.final
: In order to prevent the class from being inherited and the virtual function from being overloaded, that is, terminating inheritance + terminating virtual function overloading.
/*1. override 用法-------------------------------------------------*/
struct Base {
virtual void foo(int);
};
struct SubClass: Base {
virtual void foo(int) override; // 合法
virtual void foo(float) override; // 非法, 父类没有此虚函数
};
/*2.final 用法------------------------------------------------------*/
// 防止类继承(对再上层的基类无效)
struct SubClass1 final: Base {
}; // 合法
struct SubClass2 : SubClass1 {
}; // 非法, SubClass1 已 final
// 防止虚函数重载
struct Base {
virtual void foo() final;
};
struct SubClass3: Base {
void foo(); // 非法, foo 已 final
};
[Explicitly prohibit using default functions]
Allows explicit declaration of adoption or rejection of compiler-builtin constructors
class Magic {
public:
Magic() = default; // 显式声明使用编译器生成的构造
Magic& operator=(const Magic&) = delete; // 显式声明拒绝编译器生成构造
Magic(int magic_number);
}
strongly typed enum
Introduce enumeration class (enumeration class), use enum class
the syntax to declare as follows, the enumeration defined in this way achieves type safety: it cannot be implicitly converted to integer , and it cannot be compared with integer numbers , nor can it be compared with different enumeration types Enumeration values can only be compared with the same enumeration value.
enum class new_enum : unsigned int {
value1,
value2,
value3 = 100,
value4 = 100
};
As you can see from the above, you can use a colon and type keyword after the enumeration type to specify the type of the enumeration value in the enumeration.
In addition, you can also output and obtain enumeration values by overloading <<
the symbol .