c++继承权限

  1. 继承有三种权限,public,proteced,private,默认不写是private

  2. 权限的最低是public,其次是protected,最高private

  3. 继承方式代表是父类属性在当前类中的最低呈现

  4. 父类中的privated属性在子类中不可访问

OpenGL模板测试流程

模板测试流程

不考虑earlyZ的情况下,fragment执行后,进行模板测试,通过后,进入深度测试

模板测试一般使用流程:

1. 启用模板缓冲写入
2. 渲染物体,更新模板缓冲
3. 禁用模板缓冲写入
4. 渲染其他物体,根据模板缓冲内容决定是否丢弃片段
使用模板测试绘制物体轮廓的例子
1
2
3
glStencilMask();
glStencilFunc(GLenum func, GLint ref, GLuint mask);
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass);
1. 开启模板测试和深度测试 2. 第一次render pass,主要是绘制,并写入模板 1. 开启模板测试和深度测试 2. glStencilMask(0xFF); 3. glStencilOp(keep, keep, replace); 4. glStencilFunc(always, 1, 0xFF); 5. 绘制物体 3. 第二次render pass, 放大物体,通过模板测试剔除非边缘像素 1. 将物体缩放变大 2. 关闭深度测试 //因为这里的边缘不需要有拓扑关系 3. 关闭模板写入glStencilMask(0x00); 4. glStencilFunc(not_equal, 1, 0xFF); 5. 绘制物体

关于OpenGL里面的Mask

1. 写入颜色是,r,g,b,a 分别与对应的mask,进行&运算后写入
2. depth也是同样道理,如果设置成true,就是允许写入,设置成false,不允许写入
3. stencil的Mask,是0xFF~0x00,之间的256个数,一般设置是0xFF,允许任意值写入,0x00是不允许写入

实现完美转发

什么是完美转发?

在理解什么是完美转发之前,需要知道什么是万能引用?

在模板推导过程中,使用T&& a,这时候,并不是类型T的右值引用,而是万能引用,如果a是左值,这时候,就是一个左值引用,如果a是右值,这时候就是一个右值引用,具体原理是发生引用折叠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename T>
void Add(T&& a, T&& b) {
cout << a << endl;
cout << b << endl;
}

int main() {

Add(4, 5); // a,b的类型会被推导成int&&
int a = 0;
int b = 0;
Add(a, b); // a,b的类型会被推导成int&

return 0;
}

根据参数的具体类型,来实例化模板,准确的生成左值引用和右值引用的实例,这就是万能引用

万能引用遇到的问题?

上面的例子中,Add函数参数虽然是类型是右值引用,但是值确实左值,导致函数内继续使用调用其他函数时,参数类型由右值变成左值,也就是无法将右值引用这个类型继续转发.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <typename T>
void AddImp(T&& a, T&& b) {
cout << a << endl;
cout << b << endl;
}

template <typename T>
void Add(T&& a, T&& b) {
AddImp(a, b);
}

int main() {

Add(4, 5);
int a = 0;
int b = 0;
Add(a, b);

return 0;
}

解决方案: std::forward

1
2
3
4
5
template <typename T>
void Add(T&& a, T&& b) {
AddImp(std::forward<T>(a), std::forward<T>(b));
}

std::forward的具体实现

1
2
3
4
5
template <class _Tp>
_Tp&& forward(typename remove_reference<_Tp>::type& __t)
{
return static_cast<_Tp&&>(__t);
}

具体分析一下,也是通过引用折叠来实现

  1. 如果_Tp的类型是int&, 通过引用折叠 int& && 折叠后是左值引用int&
  2. 如果_Tp的类型是int&&, 通过引用折叠 int&& && 折叠后是int&&

c++ 11 智能指针

智能指针

share_ptr使用

sharet_ptr构造函数和std::make_share 的区别

  1. 两个堆内存和一个堆内存,std::make_share效率更高

weak_ptr使用

  1. expired(),返回指向对堆对象是否释放
  2. use_count,share_ptr的强引用计数
  3. lock,返回share_ptr,如果释放,返回空

share_ptr线程安全话题

  1. share_ptr引用计数本身是线程安全的
  2. 一个share_ptr对象,在多个线程操作,不能保证线程安全
  3. share_ptr指向的对象本身,进行操作时,也无法保证线程安全,完全取决于指向对象是否线程安全

stl容器多线程安全时的性能考虑

code使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int main()
{
shared_ptr<Person> person1;

shared_ptr<Person> person2(nullptr);

shared_ptr<Person> person3(new Person(10));

shared_ptr<Person> person4 = std::make_shared<Person>(5); //效率更高,内存分布在一起

shared_ptr<Person> person5(std::move(person3)); // person3无法再使用

shared_ptr<Person> arary(new Person[10], deletePersonArray);

weak_ptr<Person> weak_Person = person5;

cout << weak_Person.use_count() << endl;

shared_ptr<Person> person6 = person5;

cout << weak_Person.use_count() << endl;

person5.reset();

cout << weak_Person.use_count() << endl;

person6.reset();

if (weak_Person.expired()) {
cout << weak_Person.use_count() << endl;

auto shareptr = weak_Person.lock();

cout << shareptr << endl;
}

return 0;

}

c++ 右值引用

1. 什么是右值?

有名称,可以取地址的值,是左值。
没有名称,不能取地址的值,就是右值,另外类似函数返回值这种临时变量,定义为将亡值,也是右值。
c++11中,所有的值,必属于左值,将亡值,和纯右值。

2. 左值引用,右值引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main() {

int a = 0; //ok

int& b = a; //ok,左值引用
int& c = 0; // not ok,左值引用无法引用右值

const int& d = 0; // ok,常左值引用,可以绑定右值
const int& e = a; // ok,常左值引用,可以绑定左值

int&& f = 0;//ok 右值引用绑定右值
const int && g = 0; // ok,常右值引用可以绑定右值
//但是实际上没有意义,因为绑定的右值无法修改,一般右值引用是为了实现移动语义,降低copy消耗

int&& h = a;//not ok,右值引用无法绑定左值

return 0;
}

左值引用,只能绑定左值
常左值引用,可以绑定常量左值,右值,非常量左值和右值
右值引用,只能绑定非常量右值
常右值引用,可以绑定常量右值,非常量右值

3. 讨论右值引用,要注意排除返回值优化

如果关闭返回值优化,可以参考
https://www.yhspy.com/2019/09/01/C-%E7%BC%96%E8%AF%91%E5%99%A8%E4%BC%98%E5%8C%96%E4%B9%8B-RVO-%E4%B8%8E-NRVO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Person GetPerson(){
return Person();
}

int main()
{
Person person = GetPerson();
person.print();

/*
一共执行三次构造
1 Person()默认构造函数
2 GetPerson函数返回时,生成临时对象,调用移动构造函数
3 使用临时对象,构造person,调用移动构造函数
*/
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Person&& GetPerson(){
return Person();
}

int main()
{
Person person = GetPerson();
person.print();

/*
一共执行两次次构造,这种写法是错的,会有warnning
Returning reference to local temporary object
1 Person()默认构造函数
2 右值引用,引用了已经析构的临时对象
3 使用临时对象,构造person,调用移动构造函数
*/
return 0;
}

4. 函数返回值,如果没有写左值引用,就是临时变量属于右值

1
2
3
4
5
6
7
8
9
10
11
12
Person GetPerson(){
return Person();
}

int main() {

Person person1 = GetPerson(); //调用一次构造,两次次移动构造

Person&& person2 = GetPerson(); //调用一次构造,一次移动构造

return 0;
}

理解上面person1和person2的区别,person1是根据临时变量构造了一个新的对象
person2是直接对临时变量的右值引用

注意

1
2
3
4
5
6
7
const Person& GetPerson1(){
return Person();
}

Person&& GetPerson2(){
return Person();
}

上面两种写法都是错误的,返回的是临时变量的引用,可以编译通过,但是有警告

Returning reference to local temporary object

c++中operator的重载

两种函数允许编译器进行隐士类型转换

  1. 单一参数调用成功的constructors
  2. 隐士转换操作符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Ration {
public:
//1 隐士构造函数
Ration(int a) {

}
//2 隐士类型转换函数
operator double() const {
return 5.0f;
}
};


int main()
{
Ration ration(1);
cout << ration << endl;
return 0;
}

如何阻止构造函数发生不期望的隐士类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Ration {
public:
explicit Ration(int a) {

}
};

int main()
{
Ration ration(1);

//构造函数声明为explicit 阻止隐士类型转换 ration == 2 会编译报错
if (ration == 2) {

}
return 0;
}

重载操作符可以在globe scope或者class scope中进行,但是切记不要重载 && || 操作符, 原因是改变了短路运算的语义,变成函数调用。

这里还有一个细节,c++中并未明确定义函数调用动作中各参数的评估顺序,而短路运算是从左到右的。

###重载(),当重载 () 时,不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Ration {
public:
explicit Ration(int a) {

}

int operator() (int a , int b, int c)
{
return 10;
}

int operator() (int a , int b)
{
return 5;
}
};

int main()
{
Ration ration(1);
ration(1, 2, 3);
ration(1, 3);
return 0;
}

C++ 构造函数

C++ 构造函数

  1. 默认构造
  2. copy构造
  3. 移动构造
  4. operator= 赋值函数

说明

  1. 对于赋值函数和copy构造函数来说,直接实现实现const的版本即可,如果参数不是const,会调用const,只有实现了非const的参数,才会调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person
{
public:
Person() {
cout << "Person()" << endl;
};
~Person() {
cout << "~Person()" << endl;
};

Person(const Person& Person) {
cout << "Person(Person& Person)" << endl;
}

Person(Person&& Person) {
cout << "Person(Person&& Person)" << endl;
}

Person& operator=(const Person&)
{
cout << "operator=(const Person&)" << endl;
return *this;
}
};

  1. 对于构造函数,copy构造函数和移动构造函数来说,只要实现其中任何一个,剩余其他的,编译器就不会帮助生成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Person
{
public:
// Person() {
// cout << "Person()" << endl;
// };
~Person() {
cout << "~Person()" << endl;
};

// Person(const Person& Person) {
// cout << "Person(Person& Person)" << endl;
// }

Person(Person&& Person) {
cout << "Person(Person&& Person)" << endl;
}

Person& operator=(const Person&)
{
cout << "operator=(const Person&)" << endl;
return *this;
}
};

int main()
{
Person person; //编译报错,找不到匹配的构造函数
return 0;

}

iOS运行时消息转发

最近读了一遍apple 文档,对于iOS运行时消息转发机制发现有些细节还是理解有所偏差,写此文章加深理解

iOS 方法调用探讨

这个话题还要从OC是一门动态语言说起,OC的动态性体现在编译和链接期,并没有直接绑定函数调用关系,编译器将方法调用转成objc_msgSend(receiver, selector, arg1, arg2, …)方法这种方式

在运行时,通过isa对象通过从子类到父类的方法查找,找到具体的函数入口进行调用,这其中还包括cache等机制,这里不在赘述,这次主要讨论的是Dynamic Method Resolution和Message Forwarding

如果调用的Seletor在类的方法列表中找不到,以实例对象的方法为例,将进入如下流程:

image.png

主要流程可以总结为,先进入消息动态处理流程,再进入消息转发流程

1.消息动态处理流程:resloveInstanceMethod中可以通过class_addMethod为此对象动态添加方法,这样就使该对象正常响应此方法

1
2
3
4
5
6
7
8
9
10
+ (BOOL)resolveInstanceMethod:(SEL)aSEL{

if (aSEL == @selector(notFoundFunctiion)) {

class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}

return [super resolveInstanceMethod:aSEL];
}

​ 需要注意的是,不仅[self notFoundFunctiion]会触发这里,在

1
[self respondsToSelector:@selector(notFoundFunctiion)]

​ 这句代码中,同样会触发消息动态处理流程,这和后面讲的消息转发有很大区别。

2.如果上面的消息处理流程返回NO,那么就会进入消息转发流程

可以将此消息转发给另外一个对象进行执行。首先进入的是forwardingTargetForSelector,这里可以返回一个可以响应此消息的对象,如果仍然返回nil,就会进入methodSignatureForSelector和forwardInvocation的流程,其实这两种方式本质的意义相同,只不过一个是通过另外的对象selector调用,一个是通过invocation的方式调用,但是要注意,这里的selector对于原来的对象来讲,respondsToSelector返回为NO

参考文章:

Objective-C Runtime Programming Guide