QT 研究

QT 研究

今天看了几个博客,觉得自己不得了了,能看 QT 源码了。

学习方式

以 QT Inside 为轴,开始看

Inside Qt Series (全集) - findumars - 博客园 (cnblogs.com)

遇到不懂的,我就递归学习

二进制兼容

其中,看了 Qt Wiki ,我了解了二进制兼容是什么。

二进制兼容,指的是,原来用 老版本Qt库,如 QT4 编译好的应用,当我们升级Qt后,还能不经过编译仍能继续使用。

这就是二进制兼容,其中涉及了老版本动态链接库和新版本动态链接库的兼容。

d-pointer

二进制兼容的实现,主要是靠 d-pointer 实现的

其中,关于 d-pointer 的解释,真的很精彩。

D-Pointer - Qt Wiki

main.cpp

//引用作为类的构造函数的形参
#include <iostream>
#include "ClassesDef.h"

using namespace std;


int main()
{
 	Widget * base = new Label();
	Label * label = new Label();
	
 	base->set_data(3);
 	cout << base->get_data() << endl;
	base->p();//实现了多态, 只调用子类的 p
 	delete base;//子类的虚构函数会调用父类的虚构函数

	
	//已知 bug: 调用 set_text 会导致程序崩溃
	//label->set_text("Hello world");
	//cout << label->get_text() << endl;
	delete label;
    return 0;
} 

验证二进制兼容

  • 第1步,用老的ClassesImpl.cpp 生成一个动态链接库ClassesImpl.dll

g++ -shared -o ClassesImpl.dll ClassesImpl.cpp

  • 第2步,用 ClassesImpl.dll + main.cpp 生成一个 app.exe

g++ main.cpp ClassesImpl.dll -o app

以后,如果有 ClassesImpl.dll , app.exe 就能正常运行,没有 ClassesImpl.dll, app.exe 就不能运行。

  • 第3步,往 ClassesImpl.cpp 加新东西,生成 ClassesImplNew.cpp

  • 第4步,用 ClassesImplNew.cpp 生成一个动态链接库 ClassesImpl.dll 覆盖原来的

  • 第5步,运行 app.exe, 看是否保持二进制兼容

app.exe 运行正常

image-20211120231956808

下面都是我递归学习的内容,体现了我 C++ 学习的薄弱。

构造函数

在复现 d-pointer 的过程中,由于父类定义了有参构造函数,而没有定义默认构造函数,导致子类也只有有参构造函数,没有默认构造函数,在这个地方,我跟教程不一样,走了一些弯路。

而父子类还要共享同一个d_pointer,子类要能继承父类的d_ptr,所以还是要理清楚,构造函数该怎么定义和使用。

下面是我的思考。

原先是这样的:

用指针向派生类的构造函数传递 d_ptr

image-20211120212605268

image-20211120212641618

image-20211120212725583

image-20211120212846494

image-20211120213001106

image-20211120214950264

但是在教程中,都是用 & 来传递的

我就不理解,还去查,人家是这么说的

image-20211120210732744

也就是说,其实传引用和传指针都可以的,传引用可能更快一些。

所以我还是采用了教程的做法,重新改了 Widget 的有参构造函数和 Label 的无参构造函数 以及 Label 的无参构造函数

修改后:

//Widget 的有参构造函数
Widget::Widget(WidgetPrivate & d): d_ptr(&d){}

//Label 的无参构造函数(其实是调用 Widget 的有参构造函数)
Label::Label(): Widget(*new LabelPrivate){}

//Label 的有参构造函数
Label::Label(LabelPrivate &d): Widget(d){}

由于 WidgetPrivete 没有默认构造函数,导致 LabelPrivate 也缺少默认构造函数,所以在调用 Label 无参构造函数时,进行 new LabelPrivate ,此时会自动调用 LabelPrivate 的默认构造函数。而LabelPrivate 缺乏默认构造函数,所以就报错了。

我参考了下面这个文章,给 WidgetPrivate 加了默认构造函数,让 派生类 LabelPrivate 也有了默认构造函数,解决了问题。

c++ - Default constructor cannot be referenced error (Visual studios 2019) - Stack Overflow

其他知识

还有几个小知识点:

构造函数小知识

构造函数初始化列表

C++构造函数初始化列表 (biancheng.net)

Widget::Widget(): d_ptr(new WidgetPrivate(this)){}

子类调用父类构造函数

Label::Label(): Widget(*new LabelPrivate){}

Label::Label(LabelPrivate &d): Widget(d){}

虚函数、纯虚函数复习

C++中的虚析构函数、纯虚析构函数具体解释 - llguanli - 博客园 (cnblogs.com)

动态绑定

虚函数的作用是什么?

是为了动态绑定

比如经典的例子:

Shape 类

Triangle 类

Circle 类

都有一个 get_area() 函数

在 Shape 类中,为了保证,基类的指针能够指向不同的派生类对象,并且能根据指向对象的不同,而能够动态的判断该调用谁的 get_area() 函数

定义了纯虚函数的类不得实例化

纯虚函数的语法:

virtual xxx

纯虚函数的主要作用,就是确保定义了纯虚函数的类不能够产生对象。

引用复习

C++中引用(&)的用法和应用实例 - 哦摩西罗伊 - 博客园 (cnblogs.com)

引用类似于指针,但是引用不能为空。

引用作为形参

引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为 这样可以避免将整块数据全部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引 用。

"hello world" 一般都是常量 const 类型的

如果函数要接受常量引用的话,那么必须加上 const 否则会因为无法将const 引用转换成普通引用而出问题

C++中引用(&)的用法和应用实例 - 哦摩西罗伊 - 博客园 (cnblogs.com)

代码
//const string & 测试
#include <iostream>
#include <string>

using namespace std;


string fun(void);

string bar(string & s); 

int main()
{
	cout << fun() << endl;
	cout << bar("hello") << endl;
	cout << bar(fun()) << endl;
	return 0;
}

string fun(void)
{
	return " ";
}

string bar(string & s)
{
	return s;
}
报错

image-20211120170349680

引用作为返回值

引用作为返回值,必须遵守以下规则:

  (1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

  (2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

不能返回函数内部 new 分配的内存空间的引用
// 不能返回函数内部new分配的内存的引用
int & fun2()
{
        int * p = new int;
       *p = 3;
        return *p;
}

int main()
{
    cout << 1 + fun2() << endl; //fun2()返回的引用没有被及时的被赋予给某个变量,导致内存泄漏
}

  (3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

  (4)引用与一些操作符的重载:

  流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回 一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一 个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这 就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。

类的相互引用

c++ 类互相引用问题? - 知乎 (zhihu.com)

进行一个前置声明即可,问题不大的。

上面这些小知识,其实都是由 前面手动实现 Widget和 Label ,在父类子类之间进行 d-pointer 共享时,采用了 指针参数、引用参数才诱发的问题,通过这些查询,我都基本理解了。

原文地址:https://www.cnblogs.com/studentWangqy/p/15583088.html