零碎知识小记
virtual
虚析构
对父指针指向的子对象执行delete,析构的时候,子对象的析构函数不会执行
class A
{
public:
~A(){
std::cout << __func__ << std::endl;
}
};
class B : public A
{
public:
~B(){
std::cout << __func__ << std::endl;
}
};
A* b = new B;
delete b; // output '~A'
{
B c;
} // output '~B ~A'
构造函数调用虚函数
基类可以调用虚函数,但调用的是基类的虚函数。子类还没有构造,不会触发多态。
class A
{
public:
A()
{
echo(); // A
}
virtual void echo()
{
std::cout << "A" << std::endl;
}
};
class B : public A
{
public:
virtual void echo()
{
std::cout << "B" << std::endl;
}
};
B b; // output 'A'
虚表
- 虚指针(vptr): 每个含有虚函数的对象里都有虚表指针,指向虚表
- 虚函数表(vtable): 顺序存放虚函数地址
- 虚指针存在于对象实例的最前面的位置,可以在多层继承或多重继承时来保证最高性能
- 虚函数表属于类,类的所有对象通过指向虚函数表的虚指针vptr来共享虚函数表
- 虚函数表vtable放在可执行文件的只读数据字段.rodata中
- 重写虚函数时,子类的函数会替换虚表中对应的函数
编译器会为每一个含有虚函数的类创建一个虚表,该虚表将被所有该类的所有对象共享,里面存储的是该类的虚函数的地址。虚表的大小是N*4(N个虚函数,一个虚函数占一行,最后以0结尾)。虚函数的实现就是通过虚表来实现的。之前讲到只有虚函数才能被覆盖,就是指的是虚表中虚函数地址被覆盖。 在有虚函数的类实例化时,编译器分配了指向该表的指针的内存(虚函数表指针vptr),简单一点就是便一起给每个对象添加了一个隐藏成员,这个隐藏成员中保存了一个指向虚函数表的指针。这意味着可以通过类实例化的地址得到虚表,然后遍历其中的函数指针,并调用相应的函数。
1,基类对象含有一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将含有一个指向独立地址表的指针。 2,如果派生类提供了基类虚函数的重定义,该虚函数表将保存新函数的地址。即就是虚函数覆盖实际是对虚函数表中的虚函数的地址的覆盖。 3,如果派生类定义了新的虚函数,则该函数的地址将被加入到虚函数表中。注意,无论类中是一个还是多个虚函数,都只需在对象中添加一个地址成员,只是表的大小不同。
RAII
含义
- 在构造函数中申请分配资源,在析构函数中释放资源。
- 利用类来管理资源,将资源与类对象的生命周期绑定
应用
- 在资源管理方面,智能指针(
std::shared_ptr
和std::unique_ptr
)是RAII
最具代表性的实现,使用了智能指针,可以实现自动的内存管理,再也不用担心忘记delete
造成内存泄漏了。 - 在状态管理方面,线程同步中使用
std::unique_lock
或std::lock_guard
对互斥量std::mutex
进行状态管理也是RAII
的典型实现,通过这种方式,我们再也不用担心互斥量之间的代码出现异常而造成线程死锁。
重载 隐藏 重写(覆盖)
三者 | 作用域 | 有无virtual | 函数名 | 形参列表 | 返回值类型 |
---|---|---|---|---|---|
重载 | 相同 | 可有可无 | 相同 | 不同 | 可同可不同 |
隐藏 | 不同 | 可有可无 | 相同 | 可同可不同 | 可同可不同 |
重写 | 不同 | 有 | 相同 | 相同 | 相同(协变) |
互斥量mutex
非定时的互斥体类
- std::mutex 已经拥有std::mutex所有权的线程不能在这个互斥体上再次调用 lock() 和 unlock(),否则可能导致死锁
- std::recursive_mutex 行为与 std::mutex 类似,区别在于能在同一个互斥体上再次调用 lock() 和 unlock(),经常用于递归函数加锁
定时的互斥体类
-
std::timed_mutex
-
std::recursive_timed_mutex
-
std::shared_timed_mutex
try_lock_for(rel_time) 在给定相对时间内获取锁,到点返回结果
try_lock_util(abs_time) 获取锁直到给定的绝对时间
仿函数(functor)
通过重载operator ()运算符模拟函数形为的类。
Qt ConnectionType
-
Qt::AutoConnection:如果接收者位于发出信号的线程中,则使用 Qt::DirectConnection。否则,使用 Qt::QueuedConnection。连接类型在信号发出时确定。
-
Qt::DirectConnection:发出信号时立即调用槽函数。该槽函数在发出信号线程中执行。
-
Qt::QueuedConnection:信号发出后,信号会暂时被放到一个消息队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,然后执行和信号关联的槽函数,这种方式既可以在同一线程内传递消息也可以跨线程操作。emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕,槽函数在接收者所依附线程执行。
-
Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。而且接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
-
Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是为了避免重复连接。
-
Qt::SingleShotConnection:这是一个可以使用按位 OR 与上述任何一种连接类型组合的标志。当设置了 Qt::SingleShotConnection 时,槽只会被调用一次;发出信号时,连接将自动断开。