A Tour of C++笔记

还记得2005年那个夏天,高考结束的我到沈阳北方图书城买了一本C++教材。当时在学校里,班主任曾经说计算机程序之前都是用BASIC编写的,后来有了C语言。当时我对计算机程序设计没有什么概念,仅有的经验是自己在文曲星CC800上摸索的QBASIC。没有人指点,也不知道在哪里获取信息,自己对网络还不太熟悉,纯靠自己摸索。心理琢磨QBASIC可能和BASIC挺像吧,自己不用再看BASIC了吧,直接C语言吧。于是在一整排一整排的C语言的教材中,出现了C++的字眼。我想,既然C比B更好,是不是C++就是C的加强版本啊!?你看,两个加号呢!于是,我捧了两本C++的书回家了。那个夏天,就是我这样没什么像样基础的高中毕业生,开始了自己的C++之旅。

那两本书是我为了学习程序设计而真正付钱购置的书籍,再后来,由于网络上有很多免费资源和盗版书籍资源,大学里也有图书馆,自己从未再为程序设计教材和教程支付过费用。

大学的专业不再是和程序设计有关的了,C++也慢慢淡出了。但是自己不甘,在大四的时候找了一个导师,做了一个和程序设计相关的毕业课题,又能够愉快的写C++了。

但是不知自己是头脑进水还是怎么的,无法追随自己的内心。本科毕业去读研究生,专业又是和程序设计没啥关系。但是自己还是强扭着要在自己的研究领域里做程序设计,此时的主要工具变成了Matlab和C++。C++变的有些生疏了,好些新的特性都不知道,也没有再系统的学习了,用到哪stack overflow到哪。毕业论文就是这样硬生生磨出了几万行C++程序。

经过8年抗战,终于拿到了博士学位,但是此时的我,又面临的人生的抉择:是做自己擅长的,还是做自己喜欢的?是抗争还是妥协?

答案是一句歌词:“不自量力的回首,之至死方休。”

做了那么多年,杂七杂八的事,我还是想写程序啊,此间的少年,还是那个高中毕业后在家苦苦琢磨类和继承的我啊。转了一大圈,发现生活是不能妥协的。

于是,放弃了博士专业擅长的工作,我要去一个能尽情写程序的地方了。

现在我就在这种地方。

于是又开始重新学习,补充C++的知识,练习新的技巧,学习CS的专业课程,一点一点的提高。前日在youtube上看了Bjarne的CPPCON的演讲,推荐他自己写的新书。于是我又一次,自己掏钱买下了这本《A Tour of C++》,虽然电脑上有这本书的PDF,但是我感觉自己不再是那个没有收入的学生了,不能这样随意获取别人的劳动成果,更何况这是创造了我所知道的这一切的人,没有道理不表达我的感激和支持。

这本书是一个很特别的介绍C++的书,字里行间透漏着Bjarne的口吻,大家可以闲来无事看一看,我就是间断的看了大概一个多月。这篇blog就是我自己的一个非常不正规的笔记,我只是记录了我感兴趣和我觉得奇妙的地方,这并不是对书中各个实例的探讨,我想,这也不是一本这样的的书。读这本书就感觉Bjarne像个大师兄,尝试保护我们这些小学弟,不想让我们被一些没有道理而又恐怖的代码吓怯了,想让我们看到C++本原的,简单的,安详的一面。

总之,这是我第三本自己掏钱购买的C++书籍,也值得纪念一下。

2 User-Defined Types

构造函数的Member initializer list 可以用花括号代替圆括号。

enum class:

enum class Color { red, blue, green }; 
enum class Traffic_light { green, yellow, red }; 
Color color = Color::red;
Color redColor = Traffic_light::red; // Error;
int i = Color::red; // Error.
color = 1; // Error.
Color otherColor {0}; // OK.
Color anotherColor = Color{0}; // OK.

这样red就有了一个scope,不会和其他red名称混淆。于此同时,enum class是具有类型约束的,不再与整型变量实现自动转换。

3 Modularity

Translation unit: A .cpp file that is compiled by itself (including the h files it #includes) is called a translation unit. A program can consist of many thousand translation units.

C++20: module
目前尚没有变成ISO C++,但是貌似已经可以使用了。

module不同于#include,module只编译一次,存在于一个translation unit。在cpp中import module的次序不影响被import的module之间的实现。在import 一个module时,不会隐式import module的import。

将一个namespace内的所有名称都至于当前scope:

using namespace std;

只使用一个namespace内的某一个名称:

using std::swap;
swap();

在一个标志了noexcept的函数内throw,会导致std::terminate()被调用。

在一个catch块内,throw;表示再次抛出catch到的异常。

某些其他语言可以通过exception来作为返回值使用,但是C++不推荐这样做,因为在C++的实现中,throw比return要expansive很多。

如何界定一个expected error和一个failure that needs throwing an exception是有些模糊的,作者推荐了一些思路:

对于以下情况,可以设计成抛出异常

  • 非常少见的错误,例如printf()函数的错误。
  • Immediate caller难以处理的错误,或者是系统级的错误,例如网络异常,内存异常。
  • 某些实现转移到了子module,自module中可抛出异常?
  • 构造函数发生错误。
  • 没有返回值可以用做error code, 或者修改函数接口过于困难。
  • 软件设计为有一个集中处理error的结构。此时其他函数都不需要一直检查函数的执行状态。
  • recover 依赖于其他函数的执行情况?
  • callback函数,原因是调用点只有function abstraction,所以要求调用点对异常进行处理不合适。
  • 错误需要“undo”?

使用exception并不一定导致性能下降,但是过多的try会。

assert()只在debug mode下起作用。

3.6.2 Value Return

Default move constructor 是怎样工作的?
Default move assignment operator 是怎样工作的?

3.6.3 Structured Binding

struct Entry {
	string name;
	int value; 
}; 
Entry read_entry(istream& is) {
	string s;
	int i;
	is >> s >> i;
	return {s,i}; 
} 
auto [n,v] = read_entry(is);

上述代码中利用return {s, i};来完成返回值的构造。从函数返回的值可以通过auto [ n, v ]进行unpack。这种binding也可以用在一些container上

void incr(map<string,int>& m) {
	for (auto& [key,value] : m)
		++value;
} 

3.7 Advice

Don’t put a using-directive in a header file.

4 Classes

4.2.1 An Arithmetic Type

Simple operations must be inlined.

4.2.3 Initializing Containers

可以使用initializer-list constructor来创建container。

4.3 Abstract Types

class Container { 
public:
	virtual double& operator[](int) = 0;     // pure virtual functionvirtual 
	int size() const = 0; 
	virtual ~Container() {}
}; 

对于所有pure virtual function,子class必须实现这些接口。

Abstract class可以没有构造函数,因为可能没有东西需要初始化。

推荐使用override关键字,这样能够给complier更多提示以检查潜在的拼写错误或者接口不一致。

4.5.2 Hierarchy Navigation

一般使用dynamic_cast的场合是,函数接口接收一个base class的指针,传入函数的object又会被该函数返回回来,此时,需要用dynamic_cast来恢复。

4.5.3 Avoiding Resource Leaks

The code using unique_ptr will be exactly as efficient as code using the raw pointers correctly.

5. Essential Operations

5.1.1 Essential Operations

如果一个class需要一个non-trivial的析构函数,那么这个类很可能需要定义一整套构造函数和assignment operator。

class X { 
public:
	X(Sometype);            // "ordinary constructor": create an object
	X();                    // default constructorX(const X&);            // copy constructor
	X(X&&);                 // move constructor
	X& operator=(const X&); // copy assignment: clean up target and copy
	X& operator=(X&&);      // move assignment: clean up target and move
	~X();                   // destructor: clean up
	// ... 
};

当一个class具有指针成员函数时,应当具体设计copy和move的接口。

5.1.2 Convertions

A constructor taking a single argument defines a conversion from its argument type.

complex z1 = 3.14;  // z1 becomes {3.14,0.0}
Vector v1 = 7; // OK: v1 has 7 elements 

上述第二个实例中,数字7{7}具有不同含义,直接使用数字时,讲调用vector的专用构造函数,创建一个具有7元素长度的Vector。而使用{7}时是创建了一个具有一个元素7的Vector。为了能够避免这种implicit的转换,可采用如下explicit的转换定义。

class Vector { 
public:
	explicit Vector(int s);    // no implicit conversion from int to Vector
	// ... 
}; 

此时Vector v1 = 7; 将会报错。
Use explicit for constructors that take a single argument unless there is a good reason not to.

5.1.3 Member Initializer

Default member initializer.

class complex {
	double re = 0;
	double im = 0; // representation: two doubles with default value 0.0
};

5.3 Resource Management

对于vector,可使用vector.push_back(std::move(object))来避免出现argument copy。在push_back内部,object不会被再次复制。

5.4.1 Comparisons

To give identical treatment to both operands of a binary operator, such as ==, it is best defined as a free-standing function in the namespace of its class.

5.4.4 User-Defined Literals

literal operator ""

constexpr complex<double> operator""i(long double arg)     // imaginary literal 
{
	return {0, arg}; 
} 

此后,可以写

complex<double> z = 2.7182818+6.283185i; 

5.5 Advice

Return containers by value (relying on move for efficiency).

6 Templates

6.2.2 Value Template Arguments

Value arguments are useful in many contexts. For example, Buffer allows us to create arbitrarily sized buffers with no use of the free store (dynamic memory):

Buffer<char,1024> glob;  // global buffer of characters (statically allocated) 
void fct() {
	Buffer<int,10> buf; // local buffer of integers (on the stack)
	// ... 
} 

A template value argument must be a constant expression.

6.3.1 Function Templates

A function template can be a member function, but not a virtual member.

6.3.2 Function Objects

Or called functor.

template<typename T> class Less_than {
	const T val;   // value to compare against 
public:
	Less_than(const T& v) :val{v} { }
	bool operator()(const T& x) const { return x<val; } // call operator
};

template<typename C, typename P>
	// requires Sequence && Callable> 
int count(const C& c, P pred) {
	int cnt = 0;
	for (const auto& x : c)
		if (pred(x))++cnt;
	return cnt; 
}

void f(const Vector<int>& vec, const list<string>& lst, int x, const string& s) {
	cout << "number of values less than " << x << ": " << count(vec,Less_than{x}) << '\n';
	cout << "number of values less than " << s << ": " << count(lst,Less_than{s}) << '\n'; 
}

A predicate is something that we can invoke to return true or false. Also, for a simple function object like Less_than, inlining is simple, so a call of Less_than is far more efficient than an indirect function call. The ability to carry data plus their efficiency makes function objects particularly useful as arguments to algorithms

6.3.3 Lambda Expressions

作者原文:
“Using lambdas can be convenient and terse, but also obscure. For nontrivial actions (say, more than a simple expression), I prefer to name the operation so as to more clearly state its purpose and to make it available for use in several places in a program."

6.4 Template Mechanisms

6.4.2 Aliases

template<typename T> class Vector { 
public:
	using value_type = T;// ... 
};

template<typename C> 
using Value_type = typename C::value_type;     // the type of C's elements 

template<typename Container> 
void algo(Container& c) {
	Vector<Value_type<Container>> vec;        // keep results here
	// ... 
}

这里typedef好像不行。

6.5 Adivce

Use a lambda if you need a simple function object in one place only.
Use template aliases to simplify notation and hide implementation details.

7. Concepts

感觉代买变的更长了。书中写C++20会开始支持,但是我并没有仔细查看。等一等大家开始正式使用了,再看看具体怎么用。CGAL中用了好多concept的样子。

8. Library Overview

没有什么要做笔记的。

9. Strings and Regular Expressions

9.2 Strings

Returning even long strings by value is efficient.

void m2(string& s1, string& s2) {
	s1 = s1 + '\n';  // append newline
	s2 += '\n';      // append newline, may be more efficient
} 

String literal and auto.

auto s = "Cat"s;   // a std::string 
auto p = "Dog";    // a C-style string: a const char* 

9.2.1 string Implementation

Short string optimization, how many characters can a “short” string have? That’s implementation defined, but “about 14 characters” isn’t a bad guess.

9.4 Rgular Expressions

写pattern时,使用raw string literal R"()",此时可以直接书写\而不需要escape。

9.5 Advice

推荐使用stringstream进行generic value extraction。

10 Input and Output

10.5 I/O of User-Defined Types

The is>>c skips whitespace by default, but is.get(c) . 这里is是一个istream 对象。

10.7 File Streams

fstream对象可以直接用于if语句测试。

ofstream ofs {"target"};               // "o" for "output" 
if (!ofs)
	error("couldn't open 'target' for writing"); 

10.10 File System

C++目前已经提供了很多新的standard library. 就是其中一个,和boost的filesystem package很像。boost和c++的标准库是不是有什么内在联系?

path p {"abc/def/ghi.jkl"};
ofstream f {p};
p.filename();
p.stem();
p.extension();

p.filename().string();

is_directory(p);
is_regular_file(p);

10.11 Advice

Use cout for normal output and cerr for errors.

11 Containers

可以用如下方式初始化一个vector

vector<double> v ( 32, 9.9 ); // size is 32, initial value is 9.9
vector<double> v { 32, 9.9 }; // size is 2, initial values are 32 and 9.9

当不知道到vector的大小时,创建vector并reserve并不一定比直接使用push_back快。

11.2.1 Elements

当使用vector存储具有继承关系,并利用了virtual function实现多态的对象时,应当存储对象的指针,也可存储对象的unique_ptr<>。

11.2.2 Range Checking

事实上,vector[]operator并不进行范围检查,at()函数将进行范围检查。

11.4 map

标准库里map是一个binary search tree。unordered_map是一个hash table。

11.7 Advice

  • When it comes to performance, don’t trust your intuition: measure;
  • Pass a container by reference and return a container by value;
  • For a container, use the ()-initializer syntax for sizes and the {}-initializer syntax for lists of element

12 Algorithms

12.1 Introduction

有一个挺有意思的例子,将一个vector排序后进行无重复拷贝。

void f(vector<Entry>& vec, list<Entry>& lst) 
{
	sort(vec.begin(),vec.end());                      // use < for order
	unique_copy(vec.begin(),vec.end(),lst.begin());   // don't copy adjacent equal elements 
} 

上述代码要求Entry定义了<== operator。对于复制到新的container的情况

list<Entry> f(vector<Entry>& vec) {
	list<Entry> res;
	sort(vec.begin(),vec.end());
	unique_copy(vec.begin(),vec.end(),back_inserter(res));     // append to resreturn res; 
}

12.2 Use of Iterators

有iterator的场合适合使用template alias

template < typename T >
usiing Iterator = typename T::iterator; // T's iterator.

template<typename C, typename V> 
vector<Iterator<C>> find_all(C& c, V v)        // find all occurrences of v in c {
	vector<Iterator<C>> res;
	for (auto p = c.begin(); p!=c.end(); ++p)
		if (*p==v)
			res.push_back(p);
	return res;
}

Iterators are used to separate algorithms and containers.

12. 6 Algorithm Overview

作者给algorithm的简单定义是” an algorithm is a function template operating on sequences of elements. “

12.10 Advice

When writing a loop, consider whether it could be expressed as a general algorithm. 这条很有意思,loop的情况符合上面作者给algorithm下的定义。

13 Utilities

13.2.1 unique_ptr and shared_ptr

很有意思的对struct的初始化。

struct S {
	int i;
	string s;
	double d;
	// ... 
}; 

auto p1 = make_shared<S>(1,"Ankh Morpork",4.65);    // p1 is a shared_ptr 
auto p2 = make_unique<S>(2,"Oz",7.62);              // p2 is a unique_ptr 

unique_ptr 和move

void f1() {
	auto p = make_unique<int>(2);
	auto q = move(p);       // p now holds nullptr// ... 
} 

13.4 Specialized Containers

有两个container感觉挺有意思

  • tuple: A sequence of an arbitrary number of elements of arbitrary types.
  • valarray: An array of numeric values of type T; provides numeric operations.

13.4.1 array
compile time需要知道array的大小。

array<int> ax = {1,2,3};     // error size not specified

array具有一定特殊有点

void h() {
	Circle a1[10];
	array<Circle,10> a2;// ...
	Shape* p1 = a1;    // OK: disaster waiting to happen
	Shape* p2 = a2;    // error: no conversion of array to Shape*
	p1[3].draw();      // disaster 
} 

上述代码中,最后一行使用C-style的方式去索引时会发生offset计算错误。

13.4.3 pairand tuple

tuple<string,int,double> t1 {"Shark",123,3.14};    // the type is explicitly specified 
auto t2 = make_tuple(string{"Herring"},10,1.23);   // the type is deduced to tuple 
tuple t3 {"Cod"s,20,9.99};                         // the type is deduced to tuple 

上述代码中,最后一行从初始化参数推断类型是C++17的特性。

string s = get<0>(t1);      // get the first element: "Shark" 
int x = get<1>(t1);         // get the second element: 123 
double d = get<2>(t1);      // get the third element: 3.14 

如果一个tuple内,所有element的类型都是不同的,可以根据类型来进行索引。

auto s = get<string>(t1);     // get the string: "Shark" 
auto x = get<int>(t1);        // get the int: 123 
auto d = get<double>(t1);     // get the double: 3.14 

// Can also be used to write values.
get<string>(t1) = "Tuna";    // write to the string 
get<int>(t1) = 7;            // write to the int 
get<double>(t1) = 312;       // write to the double

14 Numerics

14.3.1 Parallel Algorithms

标准库里提供了一些并行数值运算的实现在内。

vector<double> v {1, 2, 3, 4, 5, 9999.99999}; 
auto s = reduce(v.begin(),v.end());      // calculate the sum using a double as the accumulator 
vector<double> large; 
// ... fill large with lots of values ... 
auto s2 = reduce(par_unseq,large.begin(),large.end()); // calculate the sum using available parallelism

15 Concurrency

15.5 Sharing Data

void f() {
	scoped_lock lck {mutex1,mutex2,mutex3};   // acquire all three locks// ... manipulate shared data ... 
}// implicitly release all mutexes 

This scoped_lockwill proceed only after acquiring all its mutexes arguments and will never block (“go to sleep”) while holding a mutex. The destructor for scoped_lock ensures that the mutexes are released when a thread leaves the scope.

Shared mutex,在单一写,多读取的结构中可以使用。

shared_mutex mx;          // a mutex that can be shared 
void reader() {
	shared_lock lck {mx};       // willing to share access with other readers
	// ... read ... 
} 

void writer() {
	unique_lock lck {mx};       // needs exclusive (unique) access
	// ... write ...
} 

16 History and Compatibility

16.3.2.1 Style Problems

Don’t declare a variable before you need it and initialize it immediately.

你可能感兴趣的:(c++,学习笔记,有感而发,c++)