跳转至

零碎知识小记

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_ptrstd::unique_ptr)是RAII最具代表性的实现,使用了智能指针,可以实现自动的内存管理,再也不用担心忘记delete造成内存泄漏了。
  • 在状态管理方面,线程同步中使用std::unique_lockstd::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

  1. Qt::AutoConnection:如果接收者位于发出信号的线程中,则使用 Qt::DirectConnection。否则,使用 Qt::QueuedConnection。连接类型在信号发出时确定。

  2. Qt::DirectConnection:发出信号时立即调用槽函数。该槽函数在发出信号线程中执行。

  3. Qt::QueuedConnection:信号发出后,信号会暂时被放到一个消息队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,然后执行和信号关联的槽函数,这种方式既可以在同一线程内传递消息也可以跨线程操作。emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕,槽函数在接收者所依附线程执行。

  4. Qt::BlockingQueuedConnection:槽函数的调用时机Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。而且接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。

  5. Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是为了避免重复连接

  6. Qt::SingleShotConnection:这是一个可以使用按位 OR 与上述任何一种连接类型组合的标志。当设置了 Qt::SingleShotConnection 时,槽只会被调用一次;发出信号时,连接将自动断开

评论