Skip to content

Commit 22c0c74

Browse files
committed
update virtual function in c++ and lambda
1 parent 908b366 commit 22c0c74

File tree

5 files changed

+286
-40
lines changed

5 files changed

+286
-40
lines changed

C++/11_Lambda.md

+58-24
Original file line numberDiff line numberDiff line change
@@ -151,37 +151,71 @@
151151
return 0;
152152
}
153153

154-
在by_val_lambda中,j 被视为一个常量,一旦初始化后不会再改变(可以认为之后只是一个跟父作用域中j同名的常量),而在by_ref_lambda中,j仍然在使用父作用域中的值。所以,在使用Lambda函数的时候,如果需要捕捉的值成为Lambda函数的常量,我们通常会使用按值传递的方式捕捉;相反的,如果需要捕捉的值成成为Lambda函数运行时的变量,则应该采用按引用方式进行捕捉。
154+
在by_val_lambda中,**j 被视为一个常量,一旦初始化后不会再改变(可以认为之后只是一个跟父作用域中j同名的常量),而在by_ref_lambda中,j仍然在使用父作用域中的值**。所以,在使用Lambda函数的时候,如果需要捕捉的值成为Lambda函数的常量,我们通常会使用按值传递的方式捕捉;相反的,如果需要捕捉的值成成为Lambda函数运行时的变量,则应该采用按引用方式进行捕捉。
155155

156156
再来看一段代码:
157157

158-
#include<iostream>
159-
using namespace std;
160-
161-
int main()
162-
{
163-
int val = 0;
164-
// auto const_val_lambda = [=](){ val = 3; }; wrong!!!
165-
166-
auto mutable_val_lambda = [=]() mutable{ val = 3; };
167-
mutable_val_lambda();
168-
cout<<val<<endl; // 0
169-
170-
auto const_ref_lambda = [&]() { val = 4; };
171-
const_ref_lambda();
172-
cout<<val<<endl; // 4
173-
174-
auto mutable_ref_lambda = [&]() mutable{ val = 5; };
175-
mutable_ref_lambda();
176-
cout<<val<<endl; // 5
177-
178-
return 0;
179-
}
158+
```c++
159+
#include<iostream>
160+
using namespace std;
161+
162+
int main()
163+
{
164+
int val = 0;
165+
// auto const_val_lambda = [=](){ val = 3; }; wrong!!!
166+
// If a lambda is marked mutable (e.g. []() mutable { }) it is allowed to mutate the values that have been captured by value.
167+
auto mutable_val_lambda = [=]() mutable{ val = 3; };
168+
mutable_val_lambda();
169+
cout<<val<<endl; // 0
170+
171+
auto const_ref_lambda = [&]() { val = 4; };
172+
const_ref_lambda();
173+
cout<<val<<endl; // 4
174+
175+
auto mutable_ref_lambda = [&]() mutable{ val = 5; };
176+
mutable_ref_lambda();
177+
cout<<val<<endl; // 5
178+
179+
return 0;
180+
}
181+
```
182+
183+
这段代码主要是用来理解Lambda表达式中的mutable关键字的。默认情况下,Lambda函数总是一个const函数,这意味着我们**不能改变按照值传递方式捕捉的变量**(可以改变引用方式捕捉的变量)。不过我们可以用 mutable 取消其常量性,这样就可以在函数体中改变变量的值。
184+
185+
再来看一个简单的例子:
186+
187+
```c++
188+
int i = 0;
189+
int* p = &i;
190+
auto l = [=]{ ++*p; };
191+
l();
192+
std::cout << i << std::endl; // outputs 1
193+
```
194+
195+
内部实现相当于下面的代码:
196+
197+
```
198+
struct lambda {
199+
int* p;
200+
lambda(int* p_) : p(p_) {}
201+
void operator()() const { ++*p; }
202+
};
203+
```
204+
205+
可以看到 operator()() 是常量成员函数,相当于声明了 p 是常量指针,如下:
206+
207+
```c++
208+
int* const p;
209+
```
210+
211+
因此不允许我们更改指针 p 的值,不过我们仍然可以更改 p 指向的值(*p)。对按照引用捕获值来说,我们的引用本身是 “const” 的(要注意引用本来就不能再指向其他对象了),但是可以改变引用指向的值。
180212

181-
这段代码主要是用来理解Lambda表达式中的mutable关键字的。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。按照规定,一个const的成员函数是不能在函数体内修改非静态成员变量的值。
182213

183214
# 更多阅读
184215

216+
[What is a lambda expression in C++11?](http://stackoverflow.com/questions/7627098/what-is-a-lambda-expression-in-c11)
217+
[Passing by constant reference in the lambda capture list](http://stackoverflow.com/questions/31179355/passing-by-constant-reference-in-the-lambda-capture-list)
218+
[Lambda: Why are captured-by-value values const, but capture-by-reference values not?](http://stackoverflow.com/questions/16764153/lambda-why-are-captured-by-value-values-const-but-capture-by-reference-values)
185219
[MSDN:C++ Lambda](https://msdn.microsoft.com/zh-cn/library/dd293608.aspx)
186220
[C++中的Lambda表达式](http://www.jellythink.com/archives/668)
187221

C++/Class.md

+149-3
Original file line numberDiff line numberDiff line change
@@ -323,20 +323,157 @@ class D:public B1,public B2;
323323
324324
C++ 中,基类必须将它的两种成员函数区分开来:一种是基类希望其派生类进行覆盖的函数,另一种是基类希望派生类直接继承而不要改变的函数。对于前者,基类通常将其定义为`虚函数(virtual)`。当我们使用指针或引用调用虚函数时,该引用将被动态绑定。根据引用或指针所绑定的对象不同,该调用可能执行基类的版本,也可执行某个派生类的版本。(成员函数如果没有被声明为虚函数,则其解析过程发生在编译时而非运行时)
325325
326+
## 虚函数
327+
326328
基类通过在其成员函数的声明语句之前加上 virtual 关键字使得该函数执行动态绑定,**任何构造函数之外的非静态函数都可以是虚函数**。如果基类把一个函数声明为虚函数,则该函数在派生类中隐式地也是虚函数(**派生类可以不重写虚函数,必须重写纯虚函数**)。[C++ primer P528]
327329
328330
C++中的虚函数的作用主要是实现多态机制。关于多态,简而言之就是用父类型的指针指向其子类的实例,然后通过父类的指针调用子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。
329331
330332
虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。每个包含有虚函数的类有一个虚表,在有虚函数的类的实例中保存了虚表的指针,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表像一个地图一样,指明了实际所应该调用的函数。
331333
332-
C++的编译器保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能)。
334+
C++的编译器保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能)。对于多继承来说,情况稍微有点复杂,先来看下面的例子:
335+
336+
```c++
337+
class ClassA {
338+
public:
339+
virtual ~ ClassA() { };
340+
341+
virtual void FunctionA() { };
342+
};
343+
344+
class ClassB {
345+
public:
346+
virtual void FunctionB() { };
347+
};
348+
349+
class ClassC : public ClassA, public ClassB {
350+
public:
351+
};
352+
353+
ClassC aObject;
354+
ClassA *pA = &aObject;
355+
ClassB *pB = &aObject;
356+
ClassC *pC = &aObject;
357+
```
358+
359+
pA,pB 和 pC 大小一样吗?要回到这个问题,需要知道多重继承中内存的布局,详细内容可以参考陈皓的[文章](http://blog.csdn.net/haoel/article/details/3081328/),简单来说,是因为:
360+
361+
> 多重继承时,以声明顺序在内存中存储A/B的空间(即虚表+数据),再存储C的数据;C中重新实现的虚函数会在A/B的虚表中取代原有的虚表项,C中新加的寻函数会加在A中虚表的最后。
362+
363+
所以,针对上面的多重继承,内存分布如下图:
364+
365+
![][2]
366+
367+
## 构造与析构
368+
369+
为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类定义其析构函数为虚函数。因为**在delete一个抽象类指针时候,必须要通过虚函数找到真正的析构函数**。如下所示是正确的用法:
370+
371+
```c++
372+
class Base
373+
{
374+
public:
375+
Base(){ cout << "Create Base..." << endl;}
376+
virtual ~Base(){ cout << "Delete Base..." <<endl;}
377+
};
378+
379+
class Derived: public Base
380+
{
381+
public:
382+
Derived(){ cout << "Create Derived..." << endl;}
383+
~Derived(){ cout << "Delete Derived..." <<endl;}
384+
};
385+
386+
void foo()
387+
{
388+
Base *pb;
389+
pb = new Derived;
390+
delete pb;
391+
// Create Base...
392+
// Create Derived...
393+
// Delete Derived...
394+
// Delete Base...
395+
}
396+
```
397+
398+
如果析构函数不加virtual,delete pb 将会导致未定义行为,对大多数编译器来说,只会执行Base的析构函数,而不是真正的Derived析构函数。这是因为如果不是virtual函数,调用哪个函数依赖于指向指针的静态类型,这里来说就是 Base。
399+
400+
**析构函数可以是纯虚的,但纯虚析构函数必须有定义体,因为析构函数的调用是在子类中隐含的**,如下示例:
401+
402+
```c++
403+
class Base
404+
{
405+
public:
406+
Base(){}
407+
// virtual ~Base(); // Link Error if we define a derived child object.
408+
virtual ~Base()=0;
409+
};
410+
Base::~Base() { } // Link Error if we define a derived child object, but with no function body.
411+
412+
class Derived: public Base
413+
{
414+
};
415+
int main(){
416+
Derived d;
417+
return 0;
418+
}
419+
```
420+
421+
## 更多
422+
423+
**虚函数要么必须有定义,要么必须声明为一个纯虚函数。**
424+
425+
> A virtual function declared in a class shall be defined, or declared pure (10.4) in that class, or both; but no diagnostic is required (3.2).
426+
> -- C++03 Standard: 10.3 Virtual functions [class.virtual]
427+
428+
这是因为定义派生类对象时,链接器需要知道虚函数表中基类的虚函数指针,如果虚函数没有定义,就找不到该指针。如下示例:
429+
430+
```c++
431+
class Base
432+
{
433+
public:
434+
virtual void test();
435+
};
436+
437+
class Derived: public Base
438+
{
439+
};
440+
int main(){
441+
Derived d; // 链接错误,如果没有该定义语句,则不会链接出错。
442+
return 0;
443+
}
444+
```
445+
446+
而对于纯虚函数来说,由于不能生成纯虚函数的对象,所以不需要知道纯虚函数的定义。不过这里有一个例外,前面有提起过如果将析构函数声明为纯虚函数,那么必须提供定义(可以为空函数体),因为派生类的析构函数隐含了对基类析构函数的调用,所以链接器必须要能够找到函数地址。
447+
448+
此外,要知道常见的不能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。分别如下:
449+
450+
1. 为什么C++不支持普通函数为虚函数?
451+
452+
普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时绑定函数。
453+
454+
2. 为什么C++不支持构造函数为虚函数?
455+
456+
构造函数一般是用来初始化对象,只有在一个对象生成之后,才能发挥多态的作用,如果将构造函数声明为virtual函数,则表现为在对象还没有生成的情况下就使用了多态机制,因而是行不通的
457+
458+
3. 为什么C++不支持内联成员函数为虚函数?
459+
460+
内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的邦定函数)
461+
462+
4. 为什么C++不支持静态成员函数为虚函数?
463+
464+
这也很简单,静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,也没有动态邦定的必要性。
465+
466+
5. 为什么C++不支持友元函数为虚函数?
467+
468+
C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。
469+
333470

334-
此外:内联函数也不能是虚函数,因为其在编译时展开。虚函数是动态绑定,运行期决定的,所以内联函数不能是虚函数。
335471

336472
[虚函数地址分配](http://www.nowcoder.com/questionTerminal/d50dbed9a0f44e8092f86927cb7c259f)
337473
[虚函数表被置为0](http://www.nowcoder.com/questionTerminal/97c2bf56369845528a109bec8cfb3556)
338474
[缺省参数是静态绑定的](http://www.nowcoder.com/questionTerminal/e4d5fe27a85d43548171f32b3bc8501a)
339475

476+
340477
# 抽象类
341478

342479
为了方便使用多态特性,常常需要在基类中定义虚拟函数。但是在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

@@ -370,6 +507,8 @@ C++的编译器保证虚函数表的指针存在于对象实例中最前面的
370507
《深度探索C++对象模型》
371508
Effective C++ 05
372509
More Effective C++ 条款 27
510+
511+
[C++对象的内存布局(上)](http://blog.csdn.net/haoel/article/details/3081328/)
373512
[C++编译器自动生成的函数](http://www.cnblogs.com/xiaoxinxd/archive/2013/01/09/effective_cpp_05.html)
374513
[如何让类对象只在栈(堆)上分配空间?](http://blog.csdn.net/hxz_qlh/article/details/13135433)
375514
[构造函数:C++](https://msdn.microsoft.com/zh-cn/library/s16xw1a8.aspx)
@@ -378,8 +517,15 @@ More Effective C++ 条款 27
378517
[C++ 抽象类](http://www.cnblogs.com/balingybj/p/4771916.html)
379518
[关于C++中的虚拟继承的一些总结](http://www.cnblogs.com/BeyondAnyTime/archive/2012/06/05/2537451.html)
380519
[类中的const成员](http://www.cnblogs.com/kaituorensheng/p/3244910.html)
520+
[C++函数中那些不可以被声明为虚函数的函数](http://blog.csdn.net/hackbuteer1/article/details/6878255)
381521

522+
[Destructors](http://en.cppreference.com/w/cpp/language/destructor)
523+
[Should a virtual function essentially have a definition](http://stackoverflow.com/questions/8642124/should-a-virtual-function-essentially-have-a-definition)
524+
[When to use virtual destructors?](http://stackoverflow.com/questions/461203/when-to-use-virtual-destructors)
525+
[Should every class have a virtual destructor?](http://stackoverflow.com/questions/353817/should-every-class-have-a-virtual-destructor)
382526

383-
[1]: http://7xrlu9.com1.z0.glb.clouddn.com/C++_Class_1.png
384527

385528

529+
[1]: http://7xrlu9.com1.z0.glb.clouddn.com/C++_Class_1.png
530+
[2]: http://7xrlu9.com1.z0.glb.clouddn.com/C++_Class_2.png
531+

C++/God.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,5 @@ cout << strlen(a); //5
132132
[Cplusplus: abs](http://www.cplusplus.com/reference/cstdlib/abs/?kw=abs)
133133
[What are all the common undefined behaviours that a C++ programmer should know about? ](http://stackoverflow.com/questions/367633/what-are-all-the-common-undefined-behaviours-that-a-c-programmer-should-know-a)
134134
[What are the common undefined/unspecified behavior for C that you run into?](http://stackoverflow.com/questions/98340/what-are-the-common-undefined-unspecified-behavior-for-c-that-you-run-into)
135-
135+
[Undefined, unspecified and implementation-defined behavior](http://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior)
136136

C++/Pattern.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# RAII
2+
3+
RAII是 Resource Acquisition Is Initialization 的缩写,意为“资源获取即初始化”。它是C++之父Bjarne Stroustrup 提出的设计理念,其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源。在RAII的指导下,C++把底层的资源管理问题提升到了对象生命周期管理的更高层次。
4+
5+
说起来,RAII的含义倒也不算复杂。用白话说就是:在类的构造函数中分配资源,在析构函数中释放资源。这样,当一个对象创建的时候,构造函数会自动地被调用;而当这个对象被释放的时候,析构函数也会被自动调用。于是乎,一个对象的生命期结束后将会不再占用资源,资源的使用是安全可靠的。
6+
C++ RAII体现出了简洁、安全、实时的特点:
7+
8+
1. 概念简洁性:让资源(包括内存和非内存资源)和对象的生命周期绑定,资源类的设计者只需用在类定义内部处理资源问题,提高了程序的可维护性;
9+
2. 类型安全性:通过资源代理对象包装资源(指针变量),并利用运算符重载提供指针运算方便使用,但对外暴露类型安全的接口;
10+
3. 异常安全性:栈语义保证对象析构函数的调用,提高了程序的健壮性;
11+
4. 释放实时性:和GC相比,RAII达到了和手动释放资源一样的实时性,因此可以承担底层开发的重任。
12+
13+
14+
15+
# 更多阅读
16+
17+
[What is meant by Resource Acquisition is Initialization (RAII)?](http://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii)
18+
[RAII and smart pointers in C++](http://stackoverflow.com/questions/395123/raii-and-smart-pointers-in-c)
19+

0 commit comments

Comments
 (0)