Linux内核是用C语言开发的,而C是面向结构,面向过程的语言,这种语言的特点是数据结构和数据操作是分离的两个部分,但是这并不代表面向过程的语言无法实现面向对象语言的一些机制,比如多态性,多态的本质在于其抽象的思维方法,而并非是其实现技巧。换句话说,用C语言也同样可以实现类似多态的机制。
Linux内核中普遍使用了这种思想,比如container_of可以通过一个基类对象得到它的子类对象。而设备驱动最常用的操作模型,open/read/write/ioctl/close则是最能够体现用C语言进行多态实现的最好案例。
linux内核没有编译器的协助,无法静态的为每个多态类生成虚拟操作表对象,所以必须要唤醒自身额外的技能来完成这个操作,该操作不能在编译期完成,必须要在运行期实现,在字符设备驱动中,实现的关键一步是def_chr_fops的定义。它的超然地位源于,def_chr_fops是将所有设备的必要操作进行了更高一层的抽象--->每个设备都需要打开操作。所以,在每个驱动的必经之路上,Linux内核插入了chrdev_open函数,在此函数中,完成了设备号到具体设备的转换和映射,并且将原来的def_chr_fops替换为通过设备号得到的驱动真正的fops。之后,所有的read/write/ioct/close将会直接调用到驱动真正的FOPS中的实现。
Linux内核中多态的延迟绑定
请教chat-gpt对这个问题的看法:
以上分析了Linux内核的多态的实现机制,现在我们再分析一下CPP中静态的多态是如何实现的:
CPP的虚函数表延迟绑定
先看一段代码:
#include <iostream>
using namespace std;class Base
{
public:virtual void fn() { cout << "In Base class" << endl; }
};
class Sub :public Base
{
public:virtual void fn() { cout << "In Sub class" << endl; }
};int main(int argc, char **argv)
{Base *p;Base bc;Sub sc;p=&bc;p->fn();p=≻p->fn();return 0;
}
编译:
调试:
通过调试我们可以看到,父子两个对象的虚函数表地址都已经打印出来了。但是两个函数表的地址究竟是静态分配的还是运行时动态给的呢?我们还不得而知,不过,如果反编译ELF文件,能够得到这两个地址,那说明虚函数表一定是静态分配,静态绑定的无疑了。看一下是不是这样:
objdump -D a.out >a.dis
从反编译文件中查找地址0x7afc50和0x7afc68,我们最后还是在ELF中找到了这两个对象的势力范围,可以看到它们确实是静态分配的。
并且,用c++filt对_ZTV4Base和_ZTV3Sub进行反命名处理,我们得到了他们属于virtual function table的最直接的证据。
结论:所以,不同于Linux内核在运行时进行的延迟绑定,CPP中虚函数的实现,是编译器负责分配静态存储并填充对象的虚函数表,从而实现静态的绑定。
同一个类的所有对象共享同一个vtable
#include <iostream>
using namespace std;class Base
{
public:virtual void fn() { cout << "In Base class" << endl; }
};
class Sub :public Base
{
public:virtual void fn() { cout << "In Sub class" << endl; }
};int main(int argc, char **argv)
{Base *p;Base bc;Sub sc;Sub mc;p=&bc;p->fn();p=≻p->fn();p=&mc;p->fn();return 0;
}
从图中可以看到,mc和sc两个对象的virtual function table 指向同一个地址
1 level pointer convert to 2 level pointer by template
#include <stdio.h>
#include <iostream>
using namespace std;static int test_function(void **p, int size)
{printf("%s line %d, p is %p, size %d.\n", __func__, __LINE__, p, size);return 0;
}template <class T>
int test_function(T &ptr, int size)
{printf("%s line %d, template 1 ptr = %p, &ptr = %p.\n", __func__, __LINE__, ptr, &ptr);return test_function((void **)&ptr, size);
}template <class T>
int test_function(T **ptr, int size)
{printf("%s line %d, template 2, ptr = %p.\n", __func__, __LINE__, ptr);return test_function((void**)ptr, size);
}int main(void)
{int *ptr;void ** newptr;printf("%s line %d, ptr %p, &ptr = %p.\n", __func__, __LINE__, ptr, &ptr);test_function(ptr, 16);test_function(&ptr, 16);test_function(newptr, 16);return 0;
}
设计模式
在敏捷软件开发,原则模式和实践这本书的第11章,介绍了一个重要的软件设计原则,依赖倒置原则。主要提到两点:
1.高层模块不应该依赖于底层模块,二者都应该依赖于抽象。
2.抽象不应该依赖于细节,细节应该依赖于抽象。
图示化就是下面的样子:
从这个角度看,/dev/下面的设备节点可以看成是这样的抽象层,也就是上面提到的设备fops.
作为公共基类,无论是驱动层还是内核层,都视fops为基类。
其实C++的多态就有点这个味道,基类定义了不可更改的接口语意,作为向上依赖。供底层和上层共享使用。这个原则是各类framework设计的核心原则。
设计模式中LISKoV替换原则
里式替换原则指出,子类型必须能够替换掉它们的基类型。
-
若对每个类型S 的对象 o1, 都存在一个类型T的对象o2, 使得在所有针对T编写的程序P中, 用o1替换o2后,程序P行为功能不变, 则S是T的子类型 .
-
子类型(sub_type ) 必须能够 替换掉 它们的基类型(base type)
这个原则 看起来理所当然, 子类继承于父类, 自然包含父类的所有的公开的方法与属性. 父类可以使用的地方,子类也 当然可以使用了. 看起来非常合理.
这个原则中隐含了一条陷阱,具体分析可以参考敏捷开发方法这本书讲的正方形不是矩形的例子。
简单来说,在使用LISKOV原则实现软件继承关系的时候,要满足基类的前置条件和后置条件。
前置条件可以理解为基类对输入参数的假设,而后置条件可以认为是基类对返回值的假设。该如何理解呢?我们这样想,当我们利用寻函数机制用基类的指针调用子类的实现的时候,需要经过两次的参数转化。
对于第一次类型转化,参数类型从输入类型转化为基类运行的类型,意思就是说,所有的基类类型,子类必须都支持,所有的基类不支持的类型,子类可以支持,也可以不支持,即便子类支持,也无法通过多态机制调用的到,通过多态机制。不允许出现基类可以识别的类型子类却无法支持的情况。从这个角度,前置条件其实就是说,子类参数类型必须包含基类的参数类型,也就是说,子类要有更弱的前置条件。
对于第二次类型转化,则是从子类转化为基类,要求所有的子类的返回对象,基类都必须支持。和前置条件相比,后置条件的类型转化方向恰恰相反。所以同样道理,被转化方应该是转化目标方的超集。所以子类要求有不变或者更强的后置条件。
总结而言,满足里氏替换原则下,子类要有不变或者更弱的前置条件,同时要有不变或者更强的前置条件。这一切都是因为基类为子类立下了标准,这就又不得不提一下OCP(开闭原则,对扩展开放,对修改关闭,多态性是其实现基础)原则了。这里无需赘言。
在C++中,正方形不能从长方形中继承,就是因为正方形完全不满足长方形的前置条件,破坏了长方形的后置条件。
这样,对于一个长方形的测试用例来说,正方形完全不满足。
retangle.setw(5);
retangle.seth(6);
assert(retangle.area() == 30);
int area(int lenght, int width)
// pre-conditions : lenght and width are positive
// post-condition : returns a positive value that is the area
{if (lenght <= 0 || width <= 0) error("area() pre-condition"); int a = lenght * width; if (a <= 0) error("area() post-condition"); return a;
}
前置条件和后置条件的要求并不仅仅限于输入参数和输出参数,还会包括对函数输入和输出结合应用场景的理解,例如上面提供的测试矩形面积的用例。其实前置条件对输入参数的要求,是virtual function的基本要求,virtua function要求子类的实现和声名要和父类完全一样,否则子类的实现将会被视为另外一个虚函数而非父类虚函数的子类实现。
#include <iostream>
using namespace std;class Subsub;
class allBase
{
public:};class Base : public allBase
{
public:virtual void fn(Base *ptr) { cout << "In Base class" << endl; }
};class Sub :public Base
{
public://virtual void fn(Base *ptr) { cout << "In Sub class" << endl; }virtual void fn(Subsub* ptr) { cout << "In Sub class" << endl; }
};class Subsub: public Sub
{
public:virtual void fn(Base *ptr) { cout << "In Subsub class" << endl; }
};int main(int argc, char **argv)
{Base *p;Base bc;Sub sc;Sub mc;p=&bc;p->fn(NULL);p=≻p->fn(NULL);p=&mc;p->fn(NULL);return 0;
}
$ ./a.out
In Base class
In Base class
In Base class
条件的强弱
在逻辑上,条件强度是指条件对于结论的影响程度,如果一个条件是必要条件,那么结论无法成立或者不成立,相反,如果条件是充分条件,它可以证明结论,但是并不是结论必须成立的先决条件。
例如,考虑"这个人是犯罪分子“这个结论,如果条件是,这个人有犯罪记录,那么这个条件是必要条件,因为如果没有这个条件,结论是不成立的。相反,如果条件是,我看到这个人正在杀人,那么这个条件是充分条件,那么这个条件并不是结论成立的必要条件,但是却可以直接证明结论的有效性。
充分条件不一定是必要条件,必要条件一般也不是充分条件。满足互相之间充分且必要的条件,相当于找到等价结论。
正方形的条件要求每个边长相同,显然是一个更加强的条件,所以普通矩形不一定满足这个条件而无法作为参数输入虚函数,而作为返回值返回的时候,正方形是强条件,转换为返回值的弱条件,是可以的。
从强变为弱可以,但是从弱变为强条件,是不行的。
里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
代码里的世界观
代码中的世界和现实世界是相互映射的,就以做菜为例,如果把作菜看作是编码,那么不同的作菜方法相当于不同的编码思想。
如果忠于菜谱和作菜流程,可以类比面向过程的编程思路;如果根据不同的食材特点,因地制宜,自由组合的方式创造菜品,就可以类比面向对象的编程方式;在一些传统节日比如春节当中,我们喜欢重命名一些传统菜以适应过年的需求,比如蒸糕可以说是步步高升等等,这种比较简单可以类比编程中的封装;还有一些预置菜,可以类比代码中开发好的组件和模块,也有一些家庭DIY的菜品,对应编码中的homebrew,总之,在大佬的眼里,万物皆和编码有关。
个人的理解,现代的软件架构,面向过程和面向对象都是不可获取的,面向对过程为纬线,将不同的组建串联起来,可以想象一下任何程序都需要main函数的串联。
面向对象为经线,实现每个子模块的功能。用图示表示如下:
以下文字来自于对一片文章的相关段落翻译:
文章:Programming Paradigms, Turing Completeness and Computational Thinking
“First of all, structured programming, based on algorithm elaboration using sequence,selection and repetition is an ambiguous case. It is a core procedural/imperative programming approach for expressing algorithms through stepwise refinement withinand from modules. Alternatively, , structured programming might constitute a paradigm in its own right; Floyd [12] thought it was the dominant paradigm. For example, it is about the only applicable methodology for initial programming in contemporary graphical languages, like Alice and Scratch, which rely heavily on jigsaw like pieces for sequence,choice and iteration as basic programming elements. However, structured programming is a key methodology for OO as well as procedural programming, where it is used to elaborate algorithms within and from methods. Once classes have been identified, OO programming is indistinguishable from procedural programming. This suggests that structured programming is a subset of procedural programming, which, in turn, is a subset of OO programming. Functional programming seems markedly different as there is no concept of assignment to modify mutable memory. However, structured programming is also a key methodology for functional programming, once functions have been identified: sequence is equivalent to function composition and repetition to recursion, all functional languages have explicit selection constructs, and modern varieties provide case based function definitions. Furthermore, modular decomposition and stepwise refinement are as applicable to functional as to procedural programming. This suggests that structured programming is a subset of procedural programming, which is a subset of both OO and functional programming. It is easier to realise a programming methodology in a language that directly supports corresponding constructs, and, in practice, a pre-specified language will strongly influence the programming approach. However, all of these methodologies are, in principle, programming language independent. For example, a design derived in a procedural methodology may be directly implemented in an OO language: much contemporary initial teaching is based on this. Similarly, an OO design may be implemented in a procedural language, with disciplined use of global sub-programs and data structures. Implementing strongly imperative designs in functional languages is more complicated, with copying substituting for in-place update. In contrast, implementing functional designs that do not take strong advantage of functional abstraction is straightforward in procedural or OO languages, which now invariably support recursion. Furthermore, functional abstraction may be realised through jump tables or classes。
首先,结构化编程,在算法阐述的基础上使用序列、选择和重复是一种模糊的情况。它是一种核心的过程式/命令式编程方法,用于通过在模块内部和从模块中逐步改进来表达算法。
或者,结构化编程本身可能构成一种范式;弗洛伊德认为这是主导范式。例如,它是当代图形语言(如Alice和Scratch)中唯一适用的初始编程方法,这些语言严重依赖于像拼图一样的序列、选择和迭代作为基本编程元素。
然而,结构化编程是面向对象和过程式编程的关键方法,在过程式编程中,它用于在方法内部和从方法中详细阐述算法。一旦确定了类,OO编程就与过程式编程难以区分了。这表明结构化编程是过程式编程的子集,而过程式编程又是面向对象编程的子集。
函数式编程似乎明显不同,因为没有修改可变内存的赋值概念。然而,结构化编程也是函数式编程的一种关键方法,一旦确定了函数:序列相当于函数组合,重复相当于递归,所有函数式语言都有明确的选择结构,现代变体提供了基于case的函数定义。此外,模块化分解和逐步细化既适用于过程式编程,也适用于函数式编程。这表明结构化编程是过程式编程的子集,而过程式编程又是面向对象和函数式编程的子集。
用直接支持相应结构的语言实现编程方法更容易,而且在实践中,预先指定的语言将强烈地影响编程方法。然而,所有这些方法在原则上都是独立于编程语言的。例如,从过程方法中派生的设计可以直接用面向对象语言实现:许多当代的初始教学都是基于此。类似地,面向对象设计可以用过程语言实现,并严格使用全局子程序和数据结构。在函数式语言中实现强命令式设计要复杂得多,因为复制取代了就地更新。相反,在过程语言或OO语言中实现不充分利用函数抽象的函数式设计是很简单的,这些语言现在总是支持递归。此外,功能抽象可以通过跳转表或类来实现."
它总结出了结构化编程的核心特点,也就是使用顺序,选择和循环来阐述算法,并且提出了,结构化编程,过程编程以及面向对象和函数式编程之间的关系,他们彼此并不是毫无关联的。指出了结构化编程是所有编程范式的基础。
所以,这篇文章也印证了,面向对象编程不能孤立存在,在面向对象中总能看到结构化和面向过程的影子,相反,结构化和面向过程可以独立于OO以及函数式编程存在。
构造函数和拷贝构造函数
#include <iostream>
using namespace std;class Base
{
public:Base() {cout << "Base Constructor" << endl;}Base(const Base &b) {cout << "Base Copy Constructor" << endl;}virtual void fn() { cout << "In Base class" << endl; }~Base() {cout << "Base DeConstructor" << endl;}
};class Sub :public Base
{
public:Sub() {cout << "Sub Constructor" << endl;}Sub(const Sub &b) {cout << "Sub Copy Constructor" << endl;}virtual void fn() { cout << "In Sub class" << endl; }~Sub() {cout << "Sub DeConstructor" << endl;}
};int main(int argc, char **argv)
{Base bc;Sub sc;#if 0Sub mc;Base *p;Base bd;p=&bc;p->fn();p=≻p->fn();p=&mc;p->fn();
#elseBase fu = bc;Sub su = sc;
#endifreturn 0;
}
运行结果:
$ ./a.out
Base Constructor
Base Constructor
Sub Constructor
Base Copy Constructor
Base Constructor
Sub Copy Constructor
Sub DeConstructor
Base DeConstructor
Base DeConstructor
Sub DeConstructor
Base DeConstructor
Base DeConstructor
分别是:
bc构造和析构,对应第一行和最后一行打印。
sc构造和析构,对应2,3行和倒数2,3行打印。
fu=bc拷贝构造函数,对应第4行和倒数第4行打印。
su=sc拷贝构造函数,对应5,6行和倒数5,6行打印。
可以看到完全对称。
C语言实现继承和多态
#include <stdio.h>/* 父类结构体 */
typedef struct {int x;int y;void (*print)(void*);
} Base;/* 子类结构体 */
typedef struct {Base base; /* 父类结构体 */int z;
} Derived;/* 父类函数实现 */
void base_print(void* p) {Base* b = (Base*)p;printf("Base: (%d, %d)\n", b->x, b->y);
}/* 子类函数实现 */
void derived_print(void* p) {Derived* d = (Derived*)p;printf("Derived: (%d, %d, %d)\n", d->base.x, d->base.y, d->z);
}int main(void) {Derived d;d.base.x = 1;d.base.y = 2;d.base.print = base_print; /* 父类函数指针 */d.z = 3;d.base.print(&d); /* 调用父类函数 */d.base.print = derived_print; /* 子类函数指针 */d.base.print(&d); /* 调用子类函数 */return 0;
}
virtual function table
#include <stdio.h>
#include <stdlib.h>/* 定义基类结构体 */
typedef struct {void (**vtable)(void*);
} Base;/* 定义基类虚函数 */
void base_foo(void* obj) {printf("base_foo\n");
}/* 初始化基类虚函数表 */
void base_init_vtable(Base* obj) {obj->vtable = (void(**)(void*))malloc(sizeof(void*) * 1);obj->vtable[0] = &base_foo;
}/* 定义派生类结构体 */
typedef struct {Base base; /* 基类结构体 */int x;
} Derived;/* 定义派生类虚函数 */
void derived_foo(void* obj) {printf("derived_foo\n");
}/* 初始化派生类虚函数表 */
void derived_init_vtable(Derived* obj) {obj->base.vtable = (void(**)(void*))malloc(sizeof(void*) * 1);obj->base.vtable[0] = &derived_foo;
}/* 调用虚函数 */
void call_vfunc(Base* obj, int index) {obj->vtable[index](obj);
}int main(void) {Derived d;base_init_vtable(&d.base);call_vfunc(&d.base, 0); /* 调用基类虚函数 */derived_init_vtable(&d);call_vfunc((Base*)&d, 0); /* 调用派生类虚函数 */return 0;
}
#include <stdio.h>
#include <stdlib.h>// 定义类
typedef struct {int (*get_x)(void*);void (*set_x)(void*, int);int x;
} ClassA;// 定义类方法
int ClassA_get_x(void* obj) {ClassA* self = (ClassA*) obj;return self->x;
}void ClassA_set_x(void* obj, int x) {ClassA* self = (ClassA*) obj;self->x = x;
}// 创建对象
ClassA* ClassA_new() {ClassA* self = (ClassA*) malloc(sizeof(ClassA));self->get_x = ClassA_get_x;self->set_x = ClassA_set_x;self->x = 0;return self;
}// 定义子类
typedef struct {ClassA parent;int (*get_y)(void*);void (*set_y)(void*, int);int y;
} ClassB;// 定义子类方法
int ClassB_get_y(void* obj) {ClassB* self = (ClassB*) obj;return self->y;
}void ClassB_set_y(void* obj, int y) {ClassB* self = (ClassB*) obj;self->y = y;
}int ClassB_get_x(void* obj) {ClassA* self = (ClassA*) obj;return self->x;
}void ClassB_set_x(void* obj, int x) {ClassA* self = (ClassA*) obj;self->x = x;
}// 创建子类对象
ClassB* ClassB_new() {ClassB* self = (ClassB*) malloc(sizeof(ClassB));self->parent.get_x = ClassB_get_x;self->parent.set_x = ClassB_set_x;self->parent.x = 0;self->y = 0;self->get_y = ClassB_get_y;self->set_y = ClassB_set_y;return self;
}int main(void)
{ClassA* obj1 = ClassA_new();obj1->set_x(obj1, 123);printf("obj1->get_x() = %d\n", obj1->get_x(obj1)); // 输出 123ClassB* obj2 = ClassB_new();obj2->parent.set_x(obj2, 456);printf("obj2->parent.get_x() = %d\n", obj2->parent.get_x(obj2)); // 输出 456obj2->y = 789;printf("obj2->get_y() = %d\n", obj2->get_y(obj2)); // 输出 789free(obj2);free(obj1);return 0;
}
软件为什么要分层设计?
软件架构设计中普遍使用分层设计,各层次都有明确定义的任务,层次之间只能通过明确定义的接口与上下紧临的的层次之间通信,这种做法的好处使不同的功能组件模块化,可以互相替代。不同层次之间可以通过显示的函数名互相调用,这样用某一层的其他实现版本进行替换的时候,必须要严格遵守函数的命名。否则无法通过链接。但是也可以层次之间通过函数指针的方式,穿越各层的函数代码路径中有大量的函数指针,而没有直接函数的函数调用。这种方式可能会导致代码的执行路径更难于追踪,不够清晰。好处是由于通过函数指针进行调用,不需要严格遵守每个函数的命名。但是也不能完全避免,比如层次之间仍然需要顶一个一个函数指针表之类的对象,每一层注册自己层的一级接口到表格中。这个表格必须要遵守两层共同的约定。
C++多线程模型
C++ std::thread | 菜鸟教程
C++11之后又了标准线程库,std::thread,之前一些编译器使用C++11的编译参数是-std=c++11.
demo:
#include <thread>
#include <iostream>class wrapper
{public:wrapper(int x, int y) {this->obj1 = x;this->obj2 = y;}void member1() {while(1) {std::cout << "i am member1 " << "obj1 = " << this->obj1 << " obj2 = " << this->obj2 << "id " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(1000));}}void member2(const char *arg1, unsigned arg2) {while(1) {std::cout << "i am member2 and my first arg is (" << arg1 << ") and second arg is (" << arg2 << ")" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(1000));}}std::thread member1Thread() {return std::thread(&wrapper::member1, this);}std::thread member2Thread(const char *arg1, unsigned arg2) {return std::thread(&wrapper::member2, this, arg1, arg2);}int test_member_function(void){return this->obj1 + this->obj2;}private:int obj1;int obj2;
};void f1(int n)
{for (int i = 0; i < 5; ++i) {std::cout << "Thread " << n << " executing\n";std::this_thread::sleep_for(std::chrono::milliseconds(10));}
}int main(void)
{int ret;wrapper *w = new wrapper(1, 2);std::thread tw1 = w->member1Thread();ret = w->test_member_function();std::thread tw2 = w->member2Thread("helloworld", 6);std::thread t2(f1, 6); // pass by valuestd::cout << "after starting, tw1 joinable: " << tw1.joinable() << "id " << tw1.get_id() << std::endl;std::cout << "after starting, tw2 joinable: " << tw2.joinable() << "id " << tw2.get_id() << std::endl;std::cout << "after starting, t2 joinable: " << t2.joinable() << "id " << t2.get_id() << std::endl;std::thread tw3 = w->member2Thread("hello", 100);tw3.detach();std::cout << "after starting, tw3 joinable: " << tw3.joinable() << std::endl;t2.join();tw1.join();tw2.join();return 0;
}
编译:
g++ -g3 -O0 -std=c++11 demo.cpp
类内成员函数和全局函数做线程入口的区别:
成员函数会传递this指针,而全局静态还书则不需要,并且上一级调用函数也不同,成员函数会有成员符调用(obj.member()).而全局函数没有。
this 指针的传递
不同于结构体中指针成员的调用方式,成员函数本身是全局函数,只是多了一个隐晦的THIS指针,所以调用的时候需要额外传输THIS指针。而结构体内的指针成员则不需要,可以直接寻址,而且没有隐式的参数表达:
当成员函数返回时会调用拷贝构造函数的情况下rdi存储拷贝构造对象,rsi存放this指针
std::mutex
std::mutex是C++11标准中提供的线程同步原语,用于保护共享资源的访问,避免多个线程同时访问同一资源导致竞争问题,它的作用类似于互锁,可以在多个线程之间控制共享数据的访问。
使用std::mutex的时候,需要首先定义一个std::mutex类型的对象,斌在需要保护的代码段前后使用.lock和.unlock方法进行加锁和解锁操作,具体来说,当一个线程需要访问共享资源时,它会先调用lock方法加锁,然后执行访问操作,当访问操作完成后,再调用.unlock方法解锁,以便其它线程可以继续访问共享资源。
#include <thread>
#include <memory>
#include <mutex>
#include <iostream>typedef void (*pfunc)(void);
struct function_wrapper{pfunc fun;
};static void funct(void)
{std::cout << "fuck you here" << std::endl;
}
class wrapper
{public:wrapper(int x, int y) {this->obj1 = x;this->obj2 = y;}void member1() {while(1) {m.lock();r_m.lock();std::cout << "i am member1 " << "obj1 = " << this->obj1 << " obj2 = " << this->obj2 << " id " << std::this_thread::get_id() << std::endl;r_m.unlock();m.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(1000));}}void member2(const char *arg1, unsigned arg2) {while(1) {m.lock();r_m.lock();std::cout << "i am member2 and my first arg is (" << arg1 << ") and second arg is (" << arg2 << ")" << std::endl;r_m.unlock();m.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(1000));}}std::thread member1thread() {std::thread th1;th1 = std::thread(&wrapper::member1, this);return th1;}std::thread member2thread(const char *arg1, unsigned arg2) {return std::thread(&wrapper::member2, this, arg1, arg2);}int test_member_function(void){return this->obj1 + this->obj2;}private:int obj1;int obj2;std::mutex m;std::recursive_mutex r_m;
};void f1(int n)
{for (int i = 0; i < 5; ++i) {std::cout << "thread " << n << " executing\n";std::this_thread::sleep_for(std::chrono::milliseconds(10));}
}int main(void)
{int ret;//wrapper *w = new wrapper(1, 2);//std::shared_ptr<wrapper> w = std::make_shared<wrapper>(1, 2);auto w = std::make_shared<wrapper>(1, 2);std::thread tw1 = w->member1thread();ret = w->test_member_function();std::thread tw2 = w->member2thread("helloworld", 6);std::thread t2(f1, 6); // pass by valuestd::cout << "after starting, tw1 joinable: " << tw1.joinable() << " id " << tw1.get_id() << std::endl;std::cout << "after starting, tw2 joinable: " << tw2.joinable() << " id " << tw2.get_id() << std::endl;std::cout << "after starting, t2 joinable: " << t2.joinable() << " id " << t2.get_id() << std::endl;std::thread tw3 = w->member2thread("hello", 100);tw3.detach();std::cout << "after starting, tw3 joinable: " << tw3.joinable() << std::endl;t2.join();tw1.join();tw2.join();struct function_wrapper func_warp;func_warp.fun=funct;func_warp.fun();return 0;
}
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx; // 定义一个互斥量void print(const std::string& str) {mtx.lock(); // 加锁std::cout << str << std::endl;mtx.unlock(); // 解锁
}int main() {std::thread t1(print, "Hello, ");std::thread t2(print, "world!");t1.join();t2.join();return 0;
}
RTTI机制
RTTI机制主要由typeid,typeinfo和dynamic_cast暴露出来的功能构成,dynamic_cast通过检查虚表中typeinfo的信息判断能否在运行时集逆性指针转换以及是否需要指针偏移,需要插入额外的操作,这也揭示了dynamic_cast的开销问题。
#include <iostream>
using namespace std;class FooInterface {
public:virtual ~FooInterface() = default;virtual void Foo() = 0;
};class BarInterface {
public:virtual ~BarInterface() = default;virtual void Bar() = 0;
};class Concrete : public FooInterface, public BarInterface {
public:void Foo() override { cout << "Foo()" << endl; }void Bar() override { cout << "Bar()" << endl; }
};class Compare {
public:void test() { cout << " output" <<endl; }
};int main(void)
{Concrete c;c.Foo();c.Bar();FooInterface* foo = &c;foo->Foo();BarInterface* bar = (BarInterface*)(foo);bar->Bar(); // Prints "Foo()" - WTF?BarInterface* bar1 = dynamic_cast<BarInterface*>(foo);bar1->Bar();Compare *cmp1 = dynamic_cast<Compare*>(foo);//Compare *cmp2 = static_cast<Compare*>(foo);printf("cmp1 = %p.\n", cmp1);printf("%s line %d, bar = %p, bar1 = %p, &c=%p, typeid(*foo).name %s, typeid(*bar).name %s, typeid(*bar1).name %s.\n",\__func__, __LINE__, bar, bar1, &c, typeid(*foo).name(), typeid(*bar).name(), typeid(*bar1).name());return 0;
}
总结:
其实,在Inside The C++ Object Model(中文版名称是深度探索C++对象模型)的4.2节,已经明确给出:“在C++中,virtual funcdtions(可经由其class object被调用)可以在编译时期获得,此外,这一组地址是固定不变的,执行期不可能新增或替换之,由于程序执行时,表格的大小和内容不会改变,所以其建构和村去皆可以由编译器完全掌握,不需要执行期的任何介入”
拜编译器所赐,CPP实现多态机制几乎是透明的,程序员几乎不用做什么。但是在Linux内核中,却有另外一套优美的机制来达到同样的目的,大家殊途同归,实现了一模一样的功能。山重水复疑无路,柳暗花明又一村,有些时候,没有绝对的“是”与“否”,我们思维中固定的观念,通常也是可以调整甚至是替换的,没有绝对,没有绝对,没有绝对。
参考资料
error handling - Pre-conditions and post-condition in C++ - Stack Overflow
运用形式逻辑去分析解决问题的方法_papaofdoudou的博客-CSDN博客