Matthew Note

C\C++ Notes

双重指针

#include <stdio.h>

int main(){

    char **p=NULL;
    printf("2 level ptr is:%p\n",&p);
    printf("1 level ptr is:%p\n",p);
    return 0;
}

输出结果为

2 level ptr is:0x7fff2b440768
1 level ptr is:(nil)

分配地址传给子函数可行,指针传给子函数,让子函数分配地址回传不可行。

数组指针

char addr[256];
memset(addr+4, 0,20);

这样实际上是错误的,虽然说addr本质上是一个指针,但是在addr+4还需要强制转换为void*才可以。

memset/memcpy

具体内容man

GDB

检查core dump

1
thread apply all bt full

检查backtrace的每一层

  • up 向上一层
  • down 向下一层

显示变量类型

1
whatis (param)

常用命令

  • backtrace 显示程序中的当前位置和表示如何到达当前位置的栈跟踪(同义词:where
  • breakpoint 在程序中设置一个断点
  • cd 改变当前工作目录
  • clear 删除刚才停止处的断点
  • commands 命中断点时,列出将要执行的命令
  • continue 从断点开始继续执行
  • delete 删除一个断点或监测点;也可与其他命令一起使用
  • display 程序停止时显示变量和表达时
  • down 下移栈帧,使得另一个函数成为当前函数
  • frame 选择下一条continue命令的帧
  • info 显示与该程序有关的各种信息
  • jump 在源程序中的另一点开始运行
  • kill 异常终止在gdb 控制下运行的程序
  • list 列出相应于正在执行的程序的原文件内容
  • next 执行下一个源程序行,从而执行其整体中的一个函数
  • print 显示变量或表达式的值
  • pwd 显示当前工作目录
  • pype 显示一个数据结构(如一个结构或C++类)的内容
  • quit 退出gdb
  • reverse-search 在源文件中反向搜索正规表达式
  • run 执行该程序
  • search 在源文件中搜索正规表达式
  • set variable 给变量赋值
  • signal 将一个信号发送到正在运行的进程
  • step 执行下一个源程序行,必要时进入下一个函数
  • undisplay display 命令的反命令,不要显示表达式
  • until 结束当前循环
  • up 上移栈帧,使另一函数成为当前函数
  • watch 在程序中设置一个监测点(即数据断点)

其他小诡计

在程序中加abort() 从而可以在想拿到东西的地方得到core dump

Valgrind: Memory leak check tool

1
2
sudo apt-get install valgrind
valgrind (option) (program) (program option)
1
2
3
4
5
6
7
#0 0xb7fe1424 in __kernel_vsyscall ()
#1 0xb7d5c571 in raise () from /lib/libc.so.6
#2 0xb7d5dd72 in abort () from /lib/libc.so.6
#3 0xb7f9352f in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib/libstdc++.so.6
#4 0xb7f90eb5 in __cxxabiv1::__terminate(void (*)()) () from /usr/lib/libstdc++.so.6
#5 0xb7f90ef2 in std::terminate() () from /usr/lib/libstdc++.so.6
#6 0xb7f92155 in __cxa_pure_virtual () from /usr/lib/libstdc++.so.6

__cxa_pure_virtual() 调用虚函数,会导致一个runtime error, abort()

dynamic_cast

转换失败会返回一个NULL指针,基本只限于对于引用和指针,从父类转子类 或者从子类转父类,所谓的downcast和upcast

  • const属性用const_cast
  • 基本类型转换用static_cast
  • 多态类之间的类型转换用dynamic_cast
  • 不同类型的指针类型转换用reinterpret_cast

尽量使用C++的新式装换,尽量不用老式转换

switch-case

case 在没有{}指定范围的时候在其中声明变量会导致编译时候crosses initialization of “XXX” 错误

1
case '0'...'9': //合法

Parameters

str

C string to truncate.
Notice that this string is modified by being broken into smaller strings (tokens).
Alternativelly, a null pointer may be specified, in which case the function continues scanning where a previous successful call to the function ended.

delimiters

C string containing the delimiter characters.
These can be different from one call to another.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* strtok example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] ="- This, a sample string.";
char * pch;
printf ("Splitting string \"%s\" into tokens:\n",str);
pch = strtok (str," ,.-");
while (pch != NULL)
{
printf ("%s\n",pch);
pch = strtok (NULL, " ,.-");
}
return 0;
}

C99 支持可变长数组

基本用法

  • 变长数组只能是局部变量,不能是静态变量和全局变量,因为这两者的长度是编译时决定的,而变长数组的长度要到运行时才能确定。变长数组是局部变量,所以是有生命周期的,其生命周期仅在当前域内,即{}内。
  • 变长数组使用的内存是栈内存,所以需要注意数组长度不能太大超过栈内存大小限制。linux 上可以用 ulimit -s 查看栈大小,一般为 8M.
  • 同样适用于支持C99的C++程序

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
int n = 10;
int a[n]; /*非法,VM类型不能具有文件作用域*/
int (*p)[n]; /*非法,VM类型不能具有文件作用域*/
struct test
{
int k;
int a[n]; /*非法,a不是普通标识符*/
int (*p)[n]; /*非法,p不是普通标识符*/
};
int main( void )
{
int m = 20;
struct test1
{
int k;
int a[n]; /*非法,a不是普通标识符*/
int (*p)[n]; /*非法,a不是普通标识符*/
};
extern int a[n]; /*非法,VLA不能具有链接性*/
static int b[n]; /*非法,VLA不能具有静态存储周期*/
int c[n]; /*合法,自动VLA*/
int d[m][n]; /*合法,自动VLA*/
static int (*p1)[n] = d; /*合法,静态VM指针*/
n = 20;
static int (*p2)[n] = d; /*未定义行为*/
return 0;
}

作为形参

除了可以作为自动对象外,还可以作为函数的形参,下面几个函数原型声明是一样的:

1
2
3
4
5
6
void func( int a[m][n] );
void func( int a[*][n] );
void func( int a[ ][n] );
void func( int a[*][*] );
void func( int a[ ][*] );
void func( int (*a)[*] );

各种限定词

除了标记外,形参中的数组还可以使用类型限定词const、volatile、restrict和static关键字。由于形参中的VLA被自动调整为等效的指针,因此这些类型限定词实际上限定的是一个指针,例如:`void func( int, int, int a[const][] );等效于void func( int, int, int ( const a )[] );` 它指出a是一个const对象,不能在func内部直接通过a修改其代表的对象。例如:

1
2
3
4
5
6
7
void func( int, int, int a[const][*] );
……
void func( int m, int n, int a[const m][n] )
{
int b[m][n];
a = b; /*错误,不能通过a修改其代表的对象*/
}

static表示传入的实参的值至少要跟其所修饰的长度表达式的值一样大。例如:

1
2
3
4
5
6
7
void func( int, int, int a[const static 20][*] );
……
int m = 20, n = 10;
int a[m][n];
int b[n][m];
func( m, n, a );
func( m, n, b ); /*错误,b的第一维长度小于static 20*/

类型限定词和static关键字只能用于具有数组类型的函数形参的第一维中。这里的用词是数组类型,意味着它们不仅能用于VLA,也能用于一般数组形参。

const 函数

  • 常成员函数不能更新对象的数据成员
  • 不能调用该类中没有const修饰的成员函数
  • 当类的实例为一个const object的时候只能调用const函数
1
2
const Object* obj = new Object();
obj.func(); //这里func必须为const函数 不然就不对

友元 friend

  • 友元函数可以访问其类中的成员,并且不受private protect的限制
  • 类也可以是友元,友元类可以访问其所在类的成员
1
2
3
4
5
6
7
8
9
class A{
public
friend bool func(); //声明友元
private:
int a;
}
friend bool func(){
some code //友元的定义
}

STL和boost

  • make_pair(obj) 和 push_back(obj) 都会调用obj的拷贝构造函数,构造一个新函数,所以使用时要注意obj是否有一个合适的拷贝构造函数
  • Copy in, copy out.

shared_ptr

  • 注意循环引用(cycle)
  • 可以正确的在STL container中使用

这里bad()的使用方法会可能导致临时shared_ptr指向对象不会被正确释放(在exception case下)

1
2
3
4
5
6
7
8
9
10
void ok()
{
shared_ptr<int> p( new int(2) );
f( p, g() );
}
void bad()
{
f( shared_ptr<int>( new int(2) ), g() );
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
shared_ptr<Job> j = make_shared<Job>(); //make_shared比用new更加高效,
//这个函数会在传入时候复制一次shared_ptr,导致引用计数+1,之后return的时候又复制一次,导致引用计数又+1,但是这都会在函数执行后
//被正确释放掉
shared_ptr<Job> func(shared_ptr<Job> job){
return job;
}
//全程不会增加引用计数,但是要小心,如果这个shared_ptr的会被其他线程赋值,那么这可能会有问题
shared_ptr<Job>& func(shared_ptr<Job>& job){
return job;
}
//这个时候job会被赋值,job所指的老的对象会被释放掉,job会继续跟踪这个新建对象。
shared_ptr<Job>& func(shared_ptr<Job>& job){
job = make_shared<Job>();
return job;
}
  • 如果以引用传递shared_ptr参数,那么他不能操纵他自己,不然会编译报错
1
2
Association::addToManager(shared_ptr<Association>& assoc);
assoc->addToManager(assoc);// 这里addToManager无法接受一个assoc的引用

string是否是有引用实现的

如果是引用实现的,在多线程条件下,可能因为要加锁所以带来性能上的影响,但是如果非引用实现,每个对象都是独立的copy则无影响。

vector 的增长

vector的每次增长都会耗费一定时间去拷贝原有数据,所以如果能够预计大小,则预先reserve(size)会更好

vector赋值

最快的是用assign,其次是copy,最后是赋值操作符

STL容器

  • 迭代器(iterator) 实际上是一个指针所以
1
vector<string>::iterator it = &svec[10]

是正确的

*iter; //返回迭代器所指元素的引用
c.push_back(str); //新元素的值为str的副本
c.begin()/end(); //返回的是迭代器(即指针)
c.front()/back(); //返回的是元素的引用
  • string可以看做为一个字符容器

Allocator

虽然分配器的定制有所限制,但在许多情况下,仍需要用到自定义的分配器,而这一般是为封装对不同类型内存空间(如共享内存与已回收内存)的访问方式,或在使用内存池进行内存分配时提高性能而为。除此以外,从内存占用和运行时间的角度看,在频繁进行少量内存分配的程序中,若引入为之专门定制的分配器,也会获益良多。

任意满足分配器使用需求的C++类都可作分配器使用。具体来说,当一个类(在此设为类A)有为一个特定类型(在此设为类型T)的对象分配内存的能力时,该类就必须提供以下类型的定义:

  • A::pointer 指针
  • A::const_pointer 常量指针
  • A::reference 引用
  • A::const_reference 常量引用
  • A::value_type 值类型
  • A::size_type 所用内存大小的类型,表示类A所定义的分配模型中的单个对象最大尺寸的无符号整型
  • A::difference_type 指针差值的类型,为带符号整型,用于表示分配模型内的两个指针的差异值。

map, unordered_map

如果key是一个对象,那么需要对此对象定义一个hash function

STL TIP

  • 调用empty()去检查是否是空,而不是用size()==0检查
  • Vector<bool>会变成bitset
  • 巧用swap()可以出去vector中的多余容量
  • vector extend长度很耗时,可以事前reserve()

std::function与std::bind 函数指针

function模板类和bind模板函数,使用它们可以实现类似函数指针的功能,但却却比函数指针更加灵活,特别是函数指向类 的非静态成员函数时。

std::function可以绑定到全局函数/类静态成员函数(类静态成员函数与全局函数没有区别),如果要绑定到类的非静态成员函数,则需要使用std::bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <functional>
using namespace std;
typedef std::function<void ()> fp;
void g_fun()
{
cout<<"g_fun()"<<endl;
}
class A
{
public:
static void A_fun_static()
{
cout<<"A_fun_static()"<<endl;
}
void A_fun()
{
cout<<"A_fun()"<<endl;
}
void A_fun_int(int i)
{
cout<<"A_fun_int() "<<i<<endl;
}
//非静态类成员,因为含有this指针,所以需要使用bind
void init()
{
fp fp1=std::bind(&A::A_fun,this);
fp1();
}
void init2()
{
typedef std::function<void (int)> fpi;
//对于参数要使用占位符 std::placeholders::_1
fpi f=std::bind(&A::A_fun_int,this,std::placeholders::_1);
f(5);
}
};
int main()
{
//绑定到全局函数
fp f2=fp(&g_fun);
f2();
//绑定到类静态成员函数
fp f1=fp(&A::A_fun_static);
f1();
A().init();
A().init2();
return 0;
}

同时,std::bind绑定到虚函数时会表现出多态行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <functional>
using namespace std;
typedef std::function<void ()> fp;
class A
{
public:
virtual void f()
{
cout<<"A::f()"<<endl;
}
void init()
{
//std::bind可以表现出多态行为
fp f=std::bind(&A::f,this);
f();
}
};
class B:public A
{
public:
virtual void f()
{
cout<<"B::f()"<<endl;
}
};
int main()
{
A* pa=new B;
pa->init();
return 0;
}

预处理注释代码

#if 0可以用作注释一段代码,因为/* */不支持迭代,所以有时候用预处理方式更好

1
2
3
#if 0
//code
#endif

new operator 和 operator new

  • new operator 就是我们经常在用的new,他会分配地址,构造对象,这个不能重构。(等同于operator new,之后调用构造函数)
  • operator new 只分配内存,不构造对象,功能和malloc一样,可以重构。
  • placement new 在已有的空间上,构造对象

operator重载

重载有两种方法,一种是在类中对于类的操作符重载,另外一种是全局重载,全局重载有一个条件,就是参数至少有一个是自定义类型,这个是C++标准特别限制的

  • b.operator+(a)等同于b+a, 等同于operator+(b,a)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    int operator+(Factor &lhs, int rhs){
    //do something
    return lhs.get() + rhs;
    }
    //下面的方法是错误的
    int operator+(Int lhs, int rhs){
    //do something
    return lhs + rhs;
    }
    //类中重载的例子
    class cls{
    int operator+(cls &rhs){
    return this + rhs;
    }
    }

解决 static 属性的初始化赋值问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class c1{
private
static const size_t max = 10;
} //此种方法是通常不允许的
//可以采用以下workaround
class c1{
private
static const size_t max;
}
const size_t c1::max =10;
//或者
class c1{
private
enum{ max = 10};
}

操作符

operator char() const

这是一个类型转换的操作符

Misc

inline函数不一定inline

  • 他只是给编译器的一个建议,inline函数中不建议有循环指令
  • 在类的声明中实现的类(头文件中),默认就为inline函数
  • 不建议inline 和 static 一起使用,因为会造成大量internal副本。最新版本的C++编译器已经修复此问题。

尽量以引用和指针方式传参

  • 这样可以减少对象被构造和解析的过程
  • 尽量推迟变量声明的时间

itr++ 和 ++itr

在不考虑返回值的时候++itr效率更高,因为他不需要做一次数据拷贝,对于基本类型可以忽略,对于Class和迭代器,尽量使用前置自增(减)
所以++++itr合法,而itr++++不合法

1
2
3
4
5
6
7
8
9
10
11
//前置
UPInt& UPInt::operator++(){
*this+= 1;
return *this;
}
//后置
UPInt& UPInt::operator++(int){
UPInt oldValue = *this;
++(*this);
return oldValue;
}

using

如果子类中的属性或者局部变量的名称与父类的重名,可以用using指定使用哪个

1
using base::m_attr;

纯虚函数可以有实现

1
2
3
4
5
6
7
8
9
10
11
12
13
class base{
public:
vitural bool func() = 0;
....
}
bool base::func(){
//something
}
class derived : public base{
}
derived* d = new derived();
d->base::func(); //调用纯虚函数的默认实现

mutable 作用

mutable修饰过的属性,即便在const函数中也可以被修改。另外我们也有另一种办法来实现,就是通过一个fake this指针,在const函数中,把const this指针cast成 this,这样就可以通过这个fake this指针做赋值操作了

const 调用规则

类中二函数都存在的情况下:

  • const对象默认调用const成员函数,非const对象默认调用非const成员函数;
  • 若非const对象想调用const成员函数,则需显式转化,如(const Student&)obj.getAge();
  • 若const对象想调用非const成员函数,同理const_cast(constObj).getAge();(注意:constObj要加括号)

类中只有一函数存在的情况下:

  • 非const对象可以调用const成员函数或非const成员函数;
  • const对象只能调用const成员函数,直接调用非const函数时编译器会报错;

指针和引用

引用不可以为空,如果变量需要为空,则要用指针,如果希望不为空则尽量用引用,避免了不必要的验NULL检查

绝对不要以多态(Polymorphically)方式处理数组

数组的基本原理是依靠数组下标来寻找对应的对象,所以在多态情况下,跳过的指针偏移不一样,所以用Base指正来使用Derived类数组会有错误。

避免类中Implicit的类型装换函数

Implicit的类型转换(或者重载操作符),如果出现任何问题会非常难调试,所以尽量显示的进行类型转换,比如o.asInt(), o.asDouble()这样输出。或者以explicit修饰构造函数。

复合操作符(+=)

复合操作符(+=)比单独操作符效率要高,不需要产生临时变量

size_t

size_t 解决了在不同系统32、64位系统上 strlen类型大小的问题, 32bit系统strlen返回4字节的值,而64是8字节

全局符号

全局符号::表示调用global的变量或者方法,也适用于如下情况

1
2
//如果test类重构了operator new,则`::new`表示使用默认的new构造,而不是重构之后的
test t = ::new test();

fork execl

  • fork 会复制父进程的所有资源,包括文件描述符,会建立一个全新的pid,他的文件描述符复制父进程的
  • execl 会开始一个全新的程序全面覆盖父进程,但是他的pid依旧是是他父进程的pid,他也会继续share父进程已经打开的文件描述符,popen实际上就是调用了这个来运行一个新的程序。execl一旦运行 execl后面的code将不会被执行了,因为已经被覆盖掉了
  • vfork 父子空间共享内存空间,使得由子函数调用vfork创建的子进程(架设子进程为先执行函数的进程)调用其它函数或运行其他程序后会,父进程会出现段错误,

dup dup2

使newfd指向fd的文件

1
2
newfd = dup(fd)
dup(fd,newfd)

strncpy and snprintf

  • strncpy:如果字符串超过 size n, 那么他不会再字符串结尾加上\0
  • snprintf: 如果字符串超出 n,他会在最后一个字符加上\0, 所以这个更安全
  • strncat(char *dest, const char *src, size_t n): 最多从源中拷贝n个字符到目标串中,并在后面加一个0;也就是说,最多会有n+1个字符被写进dest。如果dest的容量为n,那么将会dest将会溢出。

execl与execlp的区别

  • execl只当前路径(不是当前路径必须加绝对路径)
  • execlp使用系统的搜索路径
  • 带l的exec函数:execl,execlp,execle,表示后边的参数以可变参数的形式给出且都以一个空指针结束。
  • 带 p的exec函数:execlp,execvp,表示第一个参数path不用输入完整路径,只有给出命令名即可,它会在环境变量PATH当中查找命令
  • 不带l的exec函数:execv,execvp表示命令所需的参数以char *arg[]形式给出且arg最后一个元素必须是NULL
  • 带e的exec函数:execle表示,将环境变量传递给需要替换的进程

system()

system函数对返回值的处理,涉及3个阶段:

  • 阶段1:创建子进程等准备工作。如果失败,返回-1。
  • 阶段2:调用/bin/sh拉起shell脚本,如果拉起失败或者shell未正常执行结束,原因值被写入到status的低8~15比特位中。system的man中只说明了会写了127这个值, 但实测发现还会写126等值。
  • 阶段3:如果shell脚本正常执行结束,将shell返回值填到status的低8~15比特位中。

判断一个system函数调用shell脚本是否正常结束的方法应该是如下3个条件同时成立:

  1. -1 != status
  2. WIFEXITED(status)为真
  3. 0 == WEXITSTATUS(status)

setbuf()

函数setbuf()用于将指定缓冲区与特定的文件流相关联,实现操作缓冲区时直接操作文件流的功能。其原型如下:
void setbuf(FILE * stream, char * buf);

unlink(char* filename)用于删除link,如果没有任何link,且所有操作这个文件的fd已经关闭,unlink会删除文件, 这是个古老的UNIX方法

unordered_map 和 map

unordered_map 要比 map更快,同样是唯一键值,如果只关心键值,可以使用unordered_set

多态和类型转换

考虑如下程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>
#include <stdio.h>
class A
{
public:
virtual void f()
{
printf("A::f()\n");
}
virtual void g()
{
//printf("A::g(), %d\n",a);
printf("A::g()\n");
}
virtual void g(int a)
{
printf("A::g(), %d\n",a);
//printf("A::g()\n");
g();
}
void h()
{
printf("A::h()\n");
f();
g();
}
//virtual
~A(){printf("destruct A\n");}
};
class B: public A
{
public:
using A::g; \\ this C++11 keyword can make the derived class call a redefinited base class function
virtual void f()
{ printf("B::f()\n");}
virtual void g()
{printf("B::g()\n");}
void h()
{printf("B::h()\n");g();f();}
void u()
{printf("B::u()\n");}
//virtual
~B(){printf("destruct B\n");}
};
int main()
{
B b;
//b.g(1);
A * p=&b;
A* p1 = new B();
p->h();
p->g();
p->g(2);
A *a = dynamic_cast<A*>(&b);
printf("\n");
a->h();
a->g();
a->g(2);
//a->u();
printf("\n");
B* pb = &b;
pb->g(2);
pb->g();
printf("\n");
delete p1;
return 0;
}

对应输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
A::h()
B::f()
B::g()
B::g()
A::g(), 2
B::g()
A::h()
B::f()
B::g()
B::g()
A::g(), 2
B::g()
A::g(), 2
B::g()
B::g()
destruct A
destruct B
destruct A

结论

  • 如果不是虚函数,指针类型的转换会导致调用对应类型的种的函数,即不为多态
  • 如果是虚函数则,按照多态显示
  • 如果是虚函数调用所在子类的非虚函数或者虚函数,则调用的都是子类中的函数,即便是非虚函数也不会调用到父类中的对应函数
  • 如果Base指针去访问一个子类中的非虚函数,则编译报错
  • 如果子类有父类的同名函数(不论是否是虚函数),则父类的所有同名的函数都被redefine(不同参数的),usingkeyword可以改进这个问题,using A::g()可以使B调用到A::g(),但是如果B已经有了g(),则无效?, 直接用Base::f()可以調用到父類的方法,但是this指针还是子类的,所以多台依旧
  • 如果不是new出的对象不用考虑析构问题,系统可以很好的析构父类和子类
  • 如果是new,需要虚析构函数,以保证父类指针也能正常析构一个指向子类的对象

C++11

auto

注意auto使用不当会导致vector被复制,而不是被引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v = {0, 1, 2, 3, 4, 5};
for (const int &i : v) // access by const reference
std::cout << i << ' ';
std::cout << '\n';
for (auto i : v) // access by value, the type of i is int
std::cout << i << ' ';
std::cout << '\n';
for (auto&& i : v) // access by reference, the type of i is int&
std::cout << i << ' ';
std::cout << '\n';
for(int n : {0,1,2,3,4,5}) // the initializer may be a braced-init-list
std::cout << n << ' ';
std::cout << '\n';
}

Range-based for loop

此方法常用语STL中内容的遍历,注意使用不当会导致容器中对象被复制 最佳用法是 for (auto& elem : range), 对于性能这和手动写的没什么区别。

for (auto elem : range) is very tempting and very bad. It produces auto elem = *__begin; (see 6.5.4 [stmt.ranged]/1), which copies each element, which is bad because:

  • It might not compile - for example, unique_ptr elements aren’t copyable. This is problematic both for users who won’t understand the resulting compiler errors, and for users writing generic code that’ll happily compile until someone instantiates it for movable-only elements.
  • It might misbehave at runtime - for example, “elem = val;” will modify the copy, but not the original element in the range. Additionally, &elem will be invalidated after each iteration.
  • It might be inefficient - for example, unnecessarily copying std::string.

重写 重载

如果父类定义了一个g(int),子类定义了一个g(),那么用子类的对象访问g(int)那么会编译出错,因为子类定义的g()函数覆盖了g(int)所以子类不能访问。
如果父类定义了一个virtual g(int),子类定义了一个virtualg(),那么用子类的对象访问(用.访问)virtual g(int)那么会编译出错,因为子类定义的virtual g()函数覆盖了virtual g(int)所以子类不能访问, 但是如果用指针(->)访问则会得到预期结果
override->重写(=覆盖)、overload->重载、polymorphism -> 多态

  • 重定义(redefining)也叫隐藏,子类重新定义父类中的非虚函数,屏蔽了父类的同名函数

    • 子类和父类的函数名称相同,但参数不同,此时不管父类函数是不是virtual函数,都将被隐藏(如果不用父类指针访问的话)。
    • 子类和父类的函数名称相同,参数也相同,但是父类函数不是virtual函数,父类的函数将被隐藏。
    • using Base::f 可以手动指定使用哪个函数(子类父类会保留下来)子类和父类冲突时,保留父类的
  • 重写(override)也称为覆盖,子类重新定义父类中有相同名称和参数的虚函数,主要在继承关系中出现。
  • 重载(overload)函数有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数之间,互相称之为重载函数。

NOTICE 除了赋值运算符重载函数以外,所有的运算符重载函数都可以被派生类继承。也就是说复制运算操作符不能被继承

attribute ((constructor))

gcc为函数提供了几种类型的属性,其中包含:构造函数(constructors)和析构函数(destructors)。
程序员应当使用类似下面的方式来指定这些属性:
static void start(void) attribute ((constructor));
static void stop(void) attribute ((destructor));
带有”构造函数”属性的函数将在main()函数之前或者动态库被load之前被执行,而声明为”析构函数”属性的函数则将在main()或者动态库unload的时候退出时执行。

多线程

互斥量

  • 注意互斥量的粒度,通常来讲对于一个变量就要对应一个互斥量,这是最优方案,另外可以增加粒度比如多个变量对应同一个互斥量,这样会影响效率,如果允许可以这样
  • 互斥量尽量存在于基类中,这样对于使用者是透明的。

pthread_attr_setscope

pthread_attr_setscope函数的作用是设置线程的在什么范围内竞争CPU资源,可以取值PTHREAD_SCOPE_SYSTEM或PTHREAD_SCOPE_PROCESS,前者表示在整个系统内竞争CPU资源,后者表示在同一进程内竞争CPU资源,默认为前者,原型如下:
int pthread_attr_setscope(pthread_attr_t *attr, int scope);

pthread_attr_setstacksize

设置线程栈的大小, 单位是字节,在默认情况下线程的栈是比较大的

pthread_attr_setdetachstate

pthread_attr_setdetachstate函数的作用是设置线程的detachedstate属性,可以取值PTHREAD_CREATE_JOINABLE和PTHREAD_CREATE_DETACHED,前者是默认值,表示其他线程可以使用pthread_join函数等待本线程结束,后者表示其他线程不可以对本线程使用pthread_join

pthread_attr_setschedpolicy

pthread_attr_setschedpolicy函数的作用是设置schedpolicy属性,即线程调度算法。schedpolicy属性值可以是SCHED_RR、SCHED_FIFO、SCHED_OTHER,其中SCHED_RR表示轮训调度,SCHED_FIFO表示先进先出调度,SCHED_OTHER表示其他。拥有管理员权限的进程才可以创建具有SCHED_RR或SCHED_FIFO调度算法的线程,一般线程的默认调度算法都是SCHED_OTHER。

pthread_once

1
2
3
4
5
#include <pthread.h>
int pthread_once(pthread_once_t *once_control,
void (*init_routine)(void));
pthread_once_t once_control = PTHREAD_ONCE_INIT;

The first call to pthread_once() by any thread in a process, with a given once_control, will call the init_routine() with no arguments. Subsequent calls of pthread_once() with the same once_control will not call the init_routine(). On return from pthread_once(), it is guaranteed that init_routine() has completed. The once_control parameter is used to determine whether the associated initialisation routine has been called.
The function pthread_once() is not a cancellation point. However, if init_routine() is a cancellation point and is canceled, the effect on once_control is as if pthread_once() was never called.

The constant PTHREAD_ONCE_INIT is defined by the header .

The behaviour of pthread_once() is undefined if once_control has automatic storage duration or is not initialised by PTHREAD_ONCE_INIT.

pthread_cond_signal()

也要配合mutex来使用,不然会可能丢失signal,考虑如下:

1
2
3
4
5
6
7
8
9
Process A Process B
pthread_mutex_lock(&mutex);
while (condition == FALSE)
condition = TRUE;
pthread_cond_signal(&cond);
pthread_cond_wait(&cond, &mutex);

如果刚好是这个情形,那么由于B没加锁,那么本来符合条件的A也再次被阻塞

fseek 线程不安全

可考虑用pread,替代fseek和fread组合

strtok()

strtok是一个线程不安全的函数,他的安全版本是strtok_r()

网络和进程通信

readv()和writev()

这两个函数类似于read和write,不过readv和writev允许单个系统调用读入到或写出自一个或多个缓冲区。这些操作分别称为分散读(scatter read)和集中写(gather write),因为来自读操作的输入数据被分散到多个应用缓冲区中,而来自应用缓冲区的输出数据则被集中提供给单个写操作。

1
2
3
4
5
6
7
8
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
struct iovec {
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
};

TCP

当服务器close了一个连接之后,如果client接着发送数据,会收到一个RST相应,client再发送数据时,就会收到一个SIGPIPE给进程,默认动作是结束client进程,我们可以使用如下方法来指定信号的处理

1
2
3
4
#include <signal.h>
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);

read and write

他们有不同的buffer,所以是全双工运行,

  • read返回0:表示连接关闭,或者说EOF
  • read返回<0: 表示发生错误,check errno

DNS Lookup

1
2
3
4
5
6
7
8
9
10
11
12
struct hostent *gethostbyname(const char *name);
int gethostbyname_r(const char *name,
struct hostent *ret, char *buf, size_t buflen,
struct hostent **result, int *h_errnop);
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
}

gethostbyname_r线程安全,gethostbyname不是线程安全的。

Network Address Convert

const char *inet_ntop(int af, const void *src,
                         char *dst, socklen_t size);
  • This function converts the network address structure src in the af address family into a character string. The resulting string is copied to the buffer pointed to by dst, which must be a non-NULL pointer. The caller specifies the number of bytes available in this buffer in the argument size.
  • 与此相似的还有inet_ntoa, 但是他是线程不安全的。
1
int inet_pton(int af, const char *src, void *dst);

反向转换

多种sockaddr

  • struct sockaddr
  • struct sockaddr_in
  • struct sockaddr_storage

网络字节序

  • htons
  • ntohs
  • htonl
  • ntohl

类内初始化和初始化列表

好像C++11之后就支持类内初始化了,在之前类内初始化是不允许的

1
2
3
4
class A{
static const int a = 7; //C++98允许
int b = 8; //C++11允许,而98不允许
}
  • 如果两者同时出现 那么初始化列表会覆盖类内初始化

虚继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A{
public:
void print(){
//
}
}
class B{
public:
void print(){
//
}
}
class C:public A, B{
}

如上例子如果我们使用c.print就会出现二义性的问题,采用虚继承可以解决这个问题

1
2
3
4
5
6
7
8
9
10
11
class C:public virtual A, B{
}
int main(){
C c();
c.print()
c.A::print()//可以指定使用哪个版本
c.B::print()
}

Deamon

一个守护进程需要fork两次,并且调用setsid(),两次是为了摆脱终端对于程序生命周期的控制,使其成为init 1进程的子进程,setsid会设置一个新的回话,不然进程会随着本回话的退出而结束。
The second fork(2) is there to ensure that the new process is not a session leader。

mmap

  • mmap allows all those processes to share the same physical memory pages, saving a lot of memory.
  • 在swap的时候,如果是使用malloc的部分要交换到swap区,但是如果是mmap则不用,因为他可以找到对应文件,在swap back的时候可以重新映射,这在处理大文件的时候非常有用
  • 限制是,如果是32位的系统,地址空间有限
  • 不会拷贝到内存空间,不论是不论是物理内存还是虚拟内存,但是占用地址

删除一个已经打开的文件

  • If the file is moved (in the same filesystem) or renamed, then the file handle remains open and can still be used to read and write the file.

  • If the file is deleted, the file handle remains open and can still be used (This is not what some people expect). The file will not really be deleted until the last handle is closed.

  • If the file is replaced by a new file, it depends exactly how. Either the file is overwritten, in which case the file handle will still be valid and access the new file, or the existing file is unlinked and a new one created with the same name, in which case it’s the same as deletion (see above).

  • In general, once the file is open, the file is open, and nobody changing the directory structure can change that - they can move, rename the file, or put something else in its place, it simply remains open.

typename

泛型编程中,经常出现泛型和变量混淆的情况例如FooClass::type * p,这里的歧义是,到底是声明一个指针,还是做乘法,这个时候我们就可以显示的告诉他typename FooClass::type* p

泛型的实例化和特例化

  • 由于泛型是在编译期间,由编译器生成类或者函数的,所以编译器需要知道类或者方法的定义,不仅仅只是声明
  • 特例化发生在,模板类不能被直接套用的时候,用tempalate <>特例化一个模板

extern “C”

理论上我们在写C++程序的时候调用C标准库都需要告诉编译器这些C标准库的函数需要extern C,但是实际上标准库都为我们做好了

1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern "C"{
#endif
//declare.
#ifdef __cplusplus
}
#endif

ranged-for

下面两者等同

1
2
3
RecordMap<T> replica_map;
for(auto& itr : replica_map){}
for(typename RecordMap<T>::iterator::value_type& itr : replica_map){}

另类Singleton

The friend declaration appears in a class body and grants a function or another class access to private and protected members of the class where the friend declaration appears.

1
2
3
4
5
6
7
8
9
10
11
Writer* getWriter(){
return new Writer();
}
class Writer{
//这里的一个声明确保了外部的同名 函数可以访问内部protected资源,也是一种单例的模式
friend Writer* getWriter();
protected:
Writer(){}
~Writer(){}
}

C++ Idioms/Execute-Around Pointer

有些时候我们需要像python装饰器一样的东西,这个时候Execute-Around Pointer是一个很好的技巧,如下code完成了在每次push_back之前后打印Size

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class VisualizableVector {
public:
class proxy {
public:
proxy (vector<int> *v) : vect (v) {
std::cout << "Before size is: " << vect->size ();
}
vector<int> * operator -> () {
return vect;
}
~proxy () {
std::cout << "After size is: " << vect->size ();
}
private:
vector <int> * vect;
};
VisualizableVector (vector<int> *v) : vect(v) {}
proxy operator -> () {
return proxy (vect);
}
private:
vector <int> * vect;
};
int main()
{
VisualizableVector vecc (new vector<int>);
//...
vecc->push_back (10); // Note use of -> operator instead of . operator
vecc->push_back (20); //等同于vecc.operator->()
}

Hack with LD_PRELOAD

这个可以用来覆盖要load的动态库中的方法或者类什么的,可以做hack,是个hook

尾递归

Tail call是一个处理递归爆栈的好方案,但是并不是说Tail call就一点栈开销的没有,另外C++有时候虽然使用了Tall call但是并没达到预期效果,因为一些对象的析构可能会影响他,可以采取一些传引用或者传指正的方法处理,不过如果能用循环就别用递归

FILE 线程安全

  • 在同一个进程内, 针对同一个FILE*的操作(比如fwrite), 是线程安全的(Windows不是哦)
  • As an example, the POSIX standard requires that C stdio FILE operations are atomic. POSIX-conforming C libraries (e.g, on Solaris and GNU/Linux) have an internal mutex to serialize operations on FILEs. However, you still need to not do stupid things like calling fclose(fs) in one thread followed by an access of fs in another.
  • 对于PIPE和FIFO, 只要写入数据不超过PIPE_BUF size那么也是atomicPIPE DOC, 但是对于文件:This volume of POSIX.1-2008 does not specify behavior of concurrent writes to a file from multiple processes. Applications should use some form of concurrency control.
  • O_APPEND
    The file is opened in append mode. Before each write(2), the file offset is positioned at the end of the file, as if with lseek(2). O_APPEND may lead to corrupted files on NFS filesystems if more than one process appends data to a file at once. This is because NFS does not support appending to a file, so the client kernel has to simulate it, which can’t be done without a race condition.
    O_APPEND DOC
  • OpenBSD 的代码里 fwrite是有文件锁的。。。

有人写了程序验证超过PIPE_BUF会不会乱序,答复是这样的:

It’s not luck, in the sense that if you dig into the kernel you can probably prove that in your particular circumstances it will never happen that one processes’ write is interleaved with another one. I am assuming that:

  • You are not hitting any file size limits
  • You are not filling the filesystem in which you create the test file
  • The file is a regular file (not a socket, pipe, or something else)
  • The filesystem is local
  • The buffer does not span multiple virtual memory mappings (this one is known to be true, because it’s malloc()ed, which puts it on the heap, which it contiguous.
  • The processes aren’t interrupted, signaled, or traced while write() is busy.
  • There are no disk I/O errors, RAM failures, or any other abnormal conditions.
  • (Maybe others)
    You will probably indeed find that if all those assumptions hold, it is the case that the kernel of the operating system you happen to be using always accomplishes a single write() system call with a single atomic contiguous write to the following file.

That doesn’t mean you can count on this always being true. You never know when it might not be true when:

the program is run on a different operating system
the file moves to an NFS filesystem
the process gets a signal while the write() is in progress and the write() returns a partial result (fewer bytes than requested). Not sure if POSIX really allows this to happen but I program defensively!
etc…
So your experiment can’t prove that you can could on non-interleaved writes.