C++多态性分析和与Linux内核中的多态性实现的共性和差异比较

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=&sc;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=&sc;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=&sc;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=&sc;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博客


结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/24439.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么有些 985、211 的本科学生即使在大学里混得很水,在找工作时学历却那么管用?...

学术分享&#xff0c;侵删 知乎&#xff1a;https://www.zhihu.com/question/354234322 编辑&#xff1a;AI有道 最近网上有个话题比较有争议&#xff1a;【为什么有些 985、211 的本科学生即使在大学里混得很水&#xff0c;在找工作时学历却那么管用&#xff1f;】笔者认为&am…

202324读书笔记|《一枝瘦骨写空山:金农画的金石气》——以一枝瘦骨,写空山之妙

202323读书笔记|《一枝瘦骨写空山&#xff1a;金农画的金石气》——以一枝瘦骨&#xff0c;写空山之妙 《一枝瘦骨写空山&#xff1a;金农画的金石气&#xff08;文人画的真性&#xff09;》作者朱良志。荷风四面&#xff0c;人在当中&#xff0c;消受这世界的清凉&#xff0c;…

超然!高效 MacBook 工作环境配置,超实用!

点击上方&#xff0c;选择“设为星标” 优质文章&#xff0c;及时送达 工欲善其事&#xff0c;必先利其器&#xff0c;工具永远都是用来解决问题的&#xff0c;没必要为了工具而工具&#xff0c;一切工具都是为了能快速准确的完成工作和学习任务而服务。 本文记录 MacBook 整个…

职业规划-IT方向(超详细,超具体)

前言 今天是周五&#xff0c;本来想好好休息。前天写了一篇博文《说出我的故事&#xff0c;献给正在迷茫的你》&#xff08;https://blog.csdn.net/weixin_44135121/article/details/92841610&#xff09;&#xff0c;不少读者留言不知该如何做职业规划&#xff0c;于是继续拖…

超然姐姐 Asp.net笔记 (网课笔记)

超然姐姐ASP.Net笔记 ASP ASP.net PHP&#xff1a;超文本预处理 2020/3/10 解决方案 ​ 项目 ​ 属性 ​ 引用 ​ 类 Forml.cs 体文件 .csproj 项目信息 .cs 源文件 记事本 输出 Console.WriteLine(" 要打印的内容"); ​ Console.ReadKey(); ​ 1.暂停当前…

超然姐姐 Linux网课笔记

超然姐姐 Linux笔记 1 .命令级接口 ​ 2.1脱机用户接口 2.程序级接口 3.图形界面 视频 mooc Linux 活跃的发行版本多少 视频mooc 自由软件是一种可以不受限制地自由使用、复制、研究、修改和分发但必须公开源代码的软件。可以买卖。这方面的不受限制正是自由软件最重要的…

抱一鸿蒙 超然之志什么意思,什么是超然?什么是洒脱?

满意答案 brickhu 2013.04.18 采纳率&#xff1a;58% 等级&#xff1a;12 已帮助&#xff1a;19811人 您好! 朋友&#xff0c;很高兴为你解答 真正的爱情并不一定是他人眼中的完美匹配 ?需要同时付出才会有意义&#xff0c;才会更加的完美? 爱是缘份&#xff0c;爱是感动&…

简单使用Knockout.js和Datatables.js 分页

添加与KnockoutJS绑定的表格数据的简单分页&#xff0c;排序和搜索的示例 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"utf-8"><meta http-equiv"X-UA-Compatible" content"IEedge"…

Python基础之文件

输出一行一行的&#xff0c;效率更高 一个任务&#xff1a; 主函数&#xff1a;

PHP登陆/php登录--【强撸项目】

强撸项目系列总目录在000集 PHP要怎么学–【思维导图知识范围】 文章目录 本系列校训本项目使用技术 上效果图phpStudy 设置导数据库 项目目录如图&#xff1a;页面代码后台代码 这么丑的界面能忍&#xff1f;配套资源作业&#xff1a; 本系列校训 用免费公开视频&#xff0…

文件处理小程序(包含哈夫曼文件压缩-解压等 C语言)

文末有源代码 文件压缩、解压、加密&#xff08;异或加密&#xff09;、解密功能等都实现了&#xff0c;但是处理后文件命名有点不合理&#xff0c;采用了加前缀的方法得到处理后的文件名&#xff0c;应该是改变后缀名。 应该这种更好&#xff1a;test.txt --- >> 压缩…

问答 | 我适合做软件开发吗?

我在微信订阅号“程序视界”的发现菜单里设置了“程序员的职业规划”和“有问有答”两个子菜单后&#xff0c;不断有人加我微信&#xff0c;找我聊程序员职场那些事儿&#xff0c;现在我线下见面聊过5个人&#xff0c;微信和QQ上聊过的人超过10个了。 我把聊天记录都汇聚在一起…

CASE WHEN的用法

1.case when语法 在SQL中&#xff0c;“Case When”语句用于选择判断&#xff0c;在执行时先对条件进行判断&#xff0c;然后根据判断结果做出相应的操作&#xff1b;格式 CASE SCORE WHEN A THEN 优 ELSE 不及格 END 2.适用场景 2.1 表结构和数据 SET NAMES utf8mb4; SET FO…

芒果Tv服务器维护,芒果tv怎么看直播?芒果tv直播看不了怎么办?

芒果tv怎么看直播 芒果TV是湖南卫视新媒体金鹰网旗下的网络电视播放器&#xff0c;为用户提供包括电视剧、电影、电视节目、新闻纪实、音乐等多种类型的点播服务。那么芒果tv怎么看直播? 1.打开【芒果TV】&#xff0c;往左划动上方的导航栏。 2.点击【直播】&#xff0c;这里就…

我脸上被软件开发刻了几个字

4 月 13 号&#xff0c;在路上&#xff0c;听着别人的微课&#xff0c;忽然想起这个话题&#xff1a;从事软件开发工作在哪些方面影响了我的生活。 稍一回想&#xff0c;不由感慨万千&#xff0c;赶紧记录下来。根据我个人的经验&#xff0c;软件开发这种工作会在下面三个方面…

user电影的详细信息和播放vue

代码 movieInfo.vue 显示图片不一样大 <template><div class"movie-container"><div class"header"><div class"header-inner clearfix"><div class"movie-info-left"><div class"avatar-s…

安装php redis扩展

查询自己使用的PHP版本 用phpinfp()函数或者在自己安装的server软件上查看自己使用的php版本 确定php版本为7.2.18&#xff0c;ts,vc15&#xff0c;服务器环境为64位. 安装php redis扩展文件 下载扩展文件 下载php redis扩展文件. 下载最新稳定的对应php版本的phpredis扩…

qa职业规划_INTP职业规划模型及个案详细解析

ISFJISTJESFJESTJISFPISTPESFPESTPINTJINTPENTJENTPINFJINFPENFJENFP 欢迎各位掌管逻辑这一社会职能的各位逻辑学家们前来&#xff0c;我会在此跟你们一起先从盘点INTP性格天赋、短板以及其具体的运用、提升途径和修炼方法&#xff0c;再延伸到INTP在现实中的专业选择、职业规划…

[MBTI]16种风格 – 第三篇

相关历史文章&#xff08;阅读本文之前&#xff0c;您可能需要先看下之前的系列&#x1f447;&#xff09; 如何做到有效的学习:学习金字塔 艾宾浩斯记忆法遗忘曲线 如何学会思考&#xff0c;来自一位粉丝的灵魂拷问 减肥追班花/我要学英语/SMART原则&#xff0c;让你做事更…

WWDC20 10041 - What's new in SwiftUI

本文知识目录 知识点问题梳理 这里罗列了四个问题用来考察你是否已经掌握了这篇文章&#xff0c;如果没有建议你加入 收藏 再次阅读&#xff1a; 都有哪些协议遵循 Scene&#xff0c;它们都有哪些功能和使用场景 &#xff1f;说说 toolbar 有哪些构造方法 &#xff1f;本文提到…