介绍
因为c++只规定了 虚继承/ 虚函数/ 多继承/ 的行为, 但将实现方法留给编译器作者. 所以各个平台的实现并不相同, 得出的结果也不尽相同.
经测试, vs和gcc目前比较统一的情况只有2种 :
- 无继承+无虚函数
- 无继承+虚函数
故本文只讨论这2种, 以及了解虚函数和虚继承的含义.
. . .
因为c++只规定了 虚继承/ 虚函数/ 多继承/ 的行为, 但将实现方法留给编译器作者. 所以各个平台的实现并不相同, 得出的结果也不尽相同.
经测试, vs和gcc目前比较统一的情况只有2种 :
故本文只讨论这2种, 以及了解虚函数和虚继承的含义.
. . .
#hello.py |
python作为一种脚本语言,我们用python写的各个module都可以包含以上那么一个累死c中的main函数,只不过python中的这种__main__
与c中有一些区别,类似于php的魔术那一套, 主要体现在:
1、当单独执行该module时,比如单独执行以上hello.py: python hello.py,则输出
This is main of module "hello.py" |
可以理解为"if __name__=="__main__":"
这一句与c中的main()函数所表述的是一致的,即作为入口;
2、当该module被其它module 引入使用时,其中的"if __name__=="__main__":"
所表示的Block不会被执行,
这是因为此时module被其它module引用时,
其__name__
的值将发生变化,__name__
的值将会是module的名字。
比如在python shell中import hello后,查看hello.__name__
:
import hello |
3、因此,在python中,当一个module作为整体被执行时,moduel.name的值将是"__main__";
而当一个module被其它module引用时,module.__name__
将是module自己的名字,
当然一个module被其它module引用时,其本身并不需要一个可执行的入口main了。
#if的后面接的是表达式 :
#if (MAX==10)||(MAX==20) |
它的作用是:如果(MAX==10)||(MAX==20)成立,那么编译器就会把其中的#if 与 #endif之间的代码编译进去(注意:是编译进去,不是执行!!)
#if后面接的是一个宏, 而#if define(x)的使用如下 :
#if defined (x) |
这个#if defined它不管里面的“x”的逻辑是“真”还是“假”它只管这个程序的前面的宏定义里面有没有定义“x”这个宏,如果定义了x这个宏,那么,编译器会编译中间的…code…否则不直接忽视中间的…code…代码。
另外 #if defined(x)也可以取反,也就用 #if !defined(x)
最后强调两点:
在编译可执行文件时需要给 gcc 加上 “-g” 选项,这样它才会为生成的可执行文件加入额外的调试信息。
不要使用编译器的优化选项,比如: “-O”,”-O2”。因为编译器会为了优化而改变程序流程,那样不利于调试。
在 GDB 中执行 shell 命令可以使用:shell command
GDB 命令可以使用 TAB 键来补全。按两次 TAB 键可以看到所有可能的匹配。
GDB 命令缩写:例如 info bre 中的 bre 相当于 breakpoints。
给 test.c 的第10行设置一个断点 :
b test.c:10
断点的删除与断点的设置同样的重要。删除断点的命令有两个:
用法:delete [breakpoints num] [range…]
delete可删除单个断点,也可删除一个断点的集合,这个集合用连续的断点号来描述。
例如:
delete 5
delete 1-10
用法:
clear 删除断点是基于行的,不是把所有的断点都删除。
例如:
clear list_insert //删除函数的所有断点
clear list.c:list_delet //删除文件:函数的所有断点
clear 12 //删除行号的所有断点
clear list.c:12 //删除文件:行号的所有断点
对断点的控制除了建立和删除外,还可以通过使能和禁止来控制,后一种方法更灵活。
断点的四种使能操作:
用法举例:
diable //禁止所有断点
disble 2 //禁止第二个断点
disable 1-5 //禁止第1到第5个断点
GDB的命令很多, 有些用得少的命令记不住的话, 可以在进入GDB之后敲 “help”, 然后再敲 “help + command_class”,
比如 :
(gdb) help
List of classes of commands:
aliases – Aliases of other commands
breakpoints – Making program stop at certain points
data – Examining data
files – Specifying and examining files
internals – Maintenance commands
obscure – Obscure features
running – Running the program
stack – Examining the stack
status – Status inquiries
support – Support facilities
tracepoints – Tracing of program execution without stopping the program
user-defined – User-defined commands
Type “help” followed by a class name for a list of commands in that class.
Type “help all” for the list of all commands.
Type “help” followed by command name for full documentation.
Type “apropos word” to search for commands related to “word”.
Command name abbreviations are allowed if unambiguous.
(gdb) help running
Running the program.
List of commands:
advance – Continue the program up to the given location (same form as args for break command)
attach – Attach to a process or file outside of GDB
continue – Continue program being debugged
detach – Detach a process or file previously attached
detach checkpoint – Detach from a checkpoint (experimental)
detach inferiors – Detach from inferior ID (or list of IDS)
disconnect – Disconnect from a target
finish – Execute until selected stack frame returns
handle – Specify how to handle signals
inferior – Use this command to switch between inferiors
interrupt – Interrupt the execution of the debugged program
jump – Continue program being debugged at specified line or address
kill – Kill execution of program being debugged
kill inferiors – Kill inferior ID (or list of IDs)
next – Step program
nexti – Step one instruction
reverse-continue – Continue program being debugged but run it in reverse
reverse-finish – Execute backward until just before selected stack frame is called
reverse-next – Step program backward
reverse-nexti – Step backward one instruction
reverse-step – Step program backward until it reaches the beginning of another source line
reverse-stepi – Step backward exactly one instruction
run – Start debugged program
…
在某些应用环境下面,一个类只允许有一个实例,这就是著名的单例模式。
单例模式分为
在实例化 m_instance
变量时,直接调用类的构造函数。顾名思义,在还未使用变量时,已经对 m_instance
进行赋值,就像很饥饿的感觉。
在main开始前就初始化好了, 所以是线程安全的。
首先给出没有考虑析构问题的饿汉模式的实现
#include <iostream> |
懒汉模式下,在定义m_instance变量时先等于NULL,在调用GetInstance()方法时,在判断是否要赋值。这种模式,并非是线程安全的,因为多个线程同时调用GetInstance()方法,就可能导致有产生多个实例。
下面是没有考虑线程安全以及析构问题的懒汉模式的代码实现
#include <iostream> |
有下面几种方法 :
使用局部静态变量。
局部静态变量的初始化是线程安全的,这一点由编译器保证.(http://gcc.gnu.org/ml/gcc-patches/2004-09/msg00265.html,这是一个 GCC 的 patch,专门解决这个问题)。会在程序退出的时候自动销毁。
见此处
这个方法适合 C++11,C++11保证静态局部变量的初始化是线程安全的。
如果是 C++98 就不能用这个方法。
class S |
或者template<typename T>
class Singleton
{
public:
static T& GetInstance()
{
static T instance;
return instance;
}
Singleton(T&&) = delete;
Singleton(const T&) = delete;
void operator= (const T&) = delete;
protected:
Singleton() = default;
virtual ~Singleton() = default;
};
线程安全,但每次都有开销。
// singleton.h |
陈硕推荐的做法
class Singleton { |
double check locking. 只能用内存屏障,其他做法都是有问题的。
参见论文: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
普通的 double check 之所以错,是因为乱序执行和多处理器下,不同 CPU 中间 cache 刷往内存并对其他 CPU 可见的顺序无法保障(cache coherency problem)。Singleton<T> *p = new Singleton<T>;
, 那么实际有 3 步:
2 和 3 的顺序是未定的(乱序执行!)。因此,如果直接赋值给 p 那么很可能构造还没完成。此时另一个线程调用 GetInstance,在 lock 外面 check 了一下,发现 p!=NULL,于是直接返回 p,使用了未初始化完成的实例,跪了。
那么,如果用中间变量转一下呢?用 tmp_p 转了下以后,tmp_p 赋值给 p 的时候,显然 p 指向的实例是构造完成了的。然而,这个 tmp_p 在编译器看来明显没什么用,会被优化掉。
上面的两个示例代码
( 没有考虑析构问题饿汉模式的示例代码 和 没有考虑线程安全与析构问题的懒汉模式的示例代码 ) 都有不能自动调用析构的问题.
当你运行这两个示例代码之后, 你都会发现并没有打印 “Singleton destruction”, 也就是说程序结束时并没有调用 singleton 类的析构函数的, 为什么呢?
因为 m_instance = new singleton;
, new出来的东西需要delete掉, 如果加上一句 delete ct; ct = NULL;
, 就会调用析构函数了.
但这种手动调用很容易忘啊, 怎么才能自动调用它的析构呢?
我们想要的是 : 自动化的正常删除该实例。
有两种方法, 我给他划分为:
我们先看第二种,
我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。
那就是定义一个内部垃圾回收GC类,并且在 singleton 中定义一个此类的静态成员。程序结束时,系统会自动析构此静态成员,此时,在此类的析构函数中析构 singleton 实例,就可以实现 m_instance 的自动释放。
#include <iostream> |
当然还有更好的方法.那就是下面这个不需要加GC内嵌类的单例模式.
在 GetInstance 方法里放一个 m_instance 的局部静态变量, 然后返回他的地址, 他就可以在程序结束自动调用析构函数.
而且这种方法在C++11也能保证线程安全.
#include <iostream> |
既要考虑线程安全又要考虑析构问题的话, 有下面几种方法 :
如果是C++11的话, 则可以使用局部静态变量, 因为C++11保证静态局部变量的初始化是线程安全的(C++98不保证), 而且也没有析构问题.
c++中对new申请的内存的释放方式有delete和delete[]两种方式,到底这两者有什么区别呢?
我们通常从教科书上看到这样的说明:
delete 释放new分配的单个对象指针指向的内存
delete[] 释放new分配的对象数组指针指向的内存
那么,按照教科书的理解,我们看下下面的代码:
int *a = new int[10]; |
肯定会有很多人说方式1肯定存在内存泄漏,是这样吗?
针对简单类型 使用new分配后的不管是数组还是非数组形式内存空间用两种方式均可 如:int *a = new int[10];
delete a;
delete [] a;
此种情况中的释放效果相同,原因在于:分配简单类型内存时,内存大小已经确定,系统可以记忆并且进行管理,在析构时,系统并不会调用析构函数,
它直接通过指针可以获取实际分配的内存空间,哪怕是一个数组内存空间(在分配过程中 系统会记录分配内存的大小等信息,此信息保存在结构体_CrtMemBlockHeader中,
具体情况可参看VC安装目录下CRT\SRC\DBGDEL.cpp)
针对类Class,两种方式体现出具体差异
当你通过下列方式分配一个类对象数组:
class A |
再看下面代码:
class A{ |
所以总结下就是,关于 new[] 和 delete[],其中又分为两种情况: