还记得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++书籍,也值得纪念一下。
构造函数的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是具有类型约束的,不再与整型变量实现自动转换。
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是有些模糊的,作者推荐了一些思路:
对于以下情况,可以设计成抛出异常
使用exception并不一定导致性能下降,但是过多的try会。
assert()
只在debug mode下起作用。
Default move constructor 是怎样工作的?
Default move assignment operator 是怎样工作的?
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;
}
Don’t put a using-directive in a header file.
Simple operations must be inlined.
可以使用initializer-list constructor来创建container。
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更多提示以检查潜在的拼写错误或者接口不一致。
一般使用dynamic_cast
的场合是,函数接口接收一个base class的指针,传入函数的object又会被该函数返回回来,此时,需要用dynamic_cast
来恢复。
The code using unique_ptr
will be exactly as efficient as code using the raw pointers correctly.
如果一个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的接口。
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.
Default member initializer.
class complex {
double re = 0;
double im = 0; // representation: two doubles with default value 0.0
};
对于vector,可使用vector.push_back(std::move(object))
来避免出现argument copy。在push_back
内部,object不会被再次复制。
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.
literal operator ""
constexpr complex<double> operator""i(long double arg) // imaginary literal
{
return {0, arg};
}
此后,可以写
complex<double> z = 2.7182818+6.283185i;
Return containers by value (relying on move for efficiency).
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.
A function template can be a member function, but not a virtual member.
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
作者原文:
“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."
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
好像不行。
Use a lambda if you need a simple function object in one place only.
Use template aliases to simplify notation and hide implementation details.
感觉代买变的更长了。书中写C++20会开始支持,但是我并没有仔细查看。等一等大家开始正式使用了,再看看具体怎么用。CGAL中用了好多concept的样子。
没有什么要做笔记的。
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*
string
ImplementationShort string optimization, how many characters can a “short” string have? That’s implementation defined, but “about 14 characters” isn’t a bad guess.
写pattern时,使用raw string literal R"()"
,此时可以直接书写\
而不需要escape。
推荐使用stringstream进行generic value extraction。
The is>>c
skips whitespace by default, but is.get(c)
. 这里is
是一个istream
对象。
fstream
对象可以直接用于if
语句测试。
ofstream ofs {"target"}; // "o" for "output"
if (!ofs)
error("couldn't open 'target' for writing");
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);
Use cout
for normal output and cerr
for errors.
可以用如下方式初始化一个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
快。
当使用vector存储具有继承关系,并利用了virtual function实现多态的对象时,应当存储对象的指针,也可存储对象的unique_ptr<>。
事实上,vector
的[]
operator并不进行范围检查,at()
函数将进行范围检查。
map
标准库里map
是一个binary search tree。unordered_map
是一个hash table。
()
-initializer syntax for sizes and the {}
-initializer syntax for lists of element有一个挺有意思的例子,将一个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;
}
有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.
作者给algorithm的简单定义是” an algorithm is a function template operating on sequences of elements. “
When writing a loop, consider whether it could be expressed as a general algorithm. 这条很有意思,loop的情况符合上面作者给algorithm下的定义。
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// ...
}
有两个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计算错误。
pair
and 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
标准库里提供了一些并行数值运算的实现在
内。
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
void f() {
scoped_lock lck {mutex1,mutex2,mutex3}; // acquire all three locks// ... manipulate shared data ...
}// implicitly release all mutexes
This scoped_lock
will proceed only after acquiring all its mutex
es arguments and will never block (“go to sleep”) while holding a mutex
. The destructor for scoped_lock
ensures that the mutex
es 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 ...
}
Don’t declare a variable before you need it and initialize it immediately.