0%

20191109记录

C++句柄、const、Boost配置、复制构造函数、列表初始化

C++ 句柄类

在容器中保存有继承关系的对象时,如果定义成保存基类对象,则派生类将被切割,如果定义成保存派生类对象,则保存基类对象又成问题(基类对象将被强制转换成派生类对象,而派生类中定义的成员未被初始化)

注: 上面这句话还是存在疑问,关键在于容器是如何保存对象的?对基类和派生类的影
响具体又是什么?

所以唯一可行的选择是容器中保存对象的指针,但是需要用户管理对象和指针。C++中一个通用的技术是包装类(cover)和句柄类(handle),使用句柄类存储和管理类指针。

句柄类大体上完成两方面的工作:

  1. 管理指针,这与智能指针的功能类似。

  2. 实现多态,利用动态绑定,使得指针既可以指向基类,也可以指向派生类。

具体的实现呢?稍后补上

参考

C++构拷贝(复制)造函数

声明一个类时拷贝构造函数似乎很常见,但总是不太清楚其作用是什么?为什么需要拷贝构造函数?什么情况下需要使用?

参考

拷贝构造函数 是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象,通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象;

  • 复制对象把它作为参数传递给函数(以值传递的方式传入函数参数);

  • 复制对象,并从函数返回这个对象。

拷贝构造函数接受一个以引用方式传入的当前类的对象作为参数,这个参数是源对象的别名。

如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果 类带有指针变量,并有动态内存分配, 则它 必须 有一个拷贝构造函数。

常见形式:

1
2
3
classname (const classname &obj){
//构造函数的主体
}

下面看一个例子,对比有复制构造函数和没有复制构造函数:

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
41
42
43
44
45
46
47
#include <iostream>
using namespace std;
class Line {
public:
//成员函数声明
int getLength(void);
Line(int len); //简单的构造函数
//Line(const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};

//成员函数定义,包括构造函数
Line::Line(int len) { //构造函数
cout << "调用构造函数" << endl;
//为指针分配内存
ptr = new int;
*ptr = len;
}

//Line::Line(const Line &obj) { // 拷贝构造函数
// cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
// ptr = new int;
// *ptr = *obj.ptr; //拷贝值
//
//}

Line::~Line(void) { // 析构函数
cout << "释放内存" << endl;
delete ptr;
}

int Line::getLength(void) {
return *ptr;
}

void display(Line obj) { //注意这里对象是以值传递的方式传入函数参数
cout << "Line 大小: " << obj.getLength() << endl;
return;
}

int main() {
Line line(10);
display(line);
return 0;
}

首先会有一个输出结果:

可以看到函数尝试调用两次析构函数以释放内存,但是当调用第二次析构函数时出现下面的情况:

最终导致程序报错,其原因主要由下图所示:

line作为变量传入到函数 display 中时,因为是浅拷贝所以 display 函数中的形参 obj 和 line 指向了相同的内存空间,而函数中的 obj 在执行完成后释放了形参指向的内存空间,但因为 line 和 obj指向相同内存,所以当程序结束尝试释放 line 指向的内存空间时发现并没有内存可以释放,这就导致了程序的错误,所以解决的办法就是使用拷贝构造函数:

1
2
3
classname (const classname &obj){
//构造函数的主体
}

下面看一个例子,对比有复制构造函数和没有复制构造函数:

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
41
42
43
44
45
46
47
#include <iostream>
using namespace std;
class Line {
public:
//成员函数声明
int getLength(void);
Line(int len); //简单的构造函数
Line(const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};

//成员函数定义,包括构造函数
Line::Line(int len) { //构造函数
cout << "调用构造函数" << endl;
//为指针分配内存
ptr = new int;
*ptr = len;
}

Line::Line(const Line &obj) { // 拷贝构造函数
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; //拷贝值

}

Line::~Line(void) { // 析构函数
cout << "释放内存" << endl;
delete ptr;
}

int Line::getLength(void) {
return *ptr;
}

void display(Line obj) { //注意这里对象是以值传递的方式传入函数参数
cout << "Line 大小: " << obj.getLength() << endl;
return;
}

int main() {
Line line(10);
display(line);
return 0;
}

程序输出:

1
2
3
4
5
调用构造函数
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
释放内存

可以看到成功调用了两次析构函数,一次是obj在 display中进行调用的, 另一次是程序结束 line 进行调用的,没有发生错误,安全退出,其结构如下:

下面还有一个例子,看一下什么时候程序会调用拷贝构造函数:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Line
{
public:
int getLength(void);
Line(int len); // 简单的构造函数
Line(const Line &obj); // 拷贝构造函数
~Line(); // 析构函数

private:
int *ptr;
};

// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "调用构造函数" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}

Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}

Line::~Line(void)
{
cout << "释放内存" << endl;
delete ptr;
}
int Line::getLength(void)
{
return *ptr;
}

void display(Line obj)
{
cout << "line 大小 : " << obj.getLength() << endl;
}

// 程序的主函数
int main()
{
Line line1(10);
cout << "==========================" << endl;
Line line2 = line1; // 这里也调用了拷贝构造函数
cout << "==========================" << endl;
display(line1);
cout << "==========================" << endl;
display(line2);
cout << "==========================" << endl;
return 0;
}

最后输出:

在对象进行复制时因为存在拷贝复制函数,所以发生了深层拷贝,最后在析构时依次析构 line1 和 line2。

c++ 函数前面和后面 使用const 的作用:

参考

前面使用const 表示返回值为const

后面加 const表示函数不可以修改class的成员

对应下面的:

1
2
3
const int getValue();

int getValue2() 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
26
27
28
29
class FunctionConst {
public:
    int value;
    FunctionConst();
    virtual ~FunctionConst();
    const int getValue();
    int getValue2() const;
};



FunctionConst::FunctionConst():value(100) {
    // TODO Auto-generated constructor stub

}

FunctionConst::~FunctionConst() {
    // TODO Auto-generated destructor stub
}

const int FunctionConst::getValue(){
    return value;//返回值是 const, 返回值是指针时,防止函数调用表达式作为左值,使得指针的内容被修改
}

int FunctionConst::getValue2() const{
    //此函数不能修改class FunctionConst的成员变量 value
    value = 15;//错误的, 因为函数后面加 const
    return value;
}

C++ 赋值初始化和列表初始化

类的构造函数在初始化成员变量时,主要有两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 方式一
CSomeClass::CSomeClass()
{
x=0;
y=1;
}


// 方式二

CSomeClass::CSomeClass() : x(0), y(1)
{
}

方式一我们可以称为赋值初始化,通过在函数体内进行赋值初始化,在所有数据成员被 分配内存空间后才进行的。

方式二我们称为列表初始化,在冒号后使用初始化列表进行初始化,给数据成员分配内存空间时就进行初始化

具体

配置boost时出现缺少“*.lib”文件解决(主要是Boost的安装和编译)

参考

主要纪录Boost的正确安装

  1. 官网下载原始文件:

这里选择红色框中的文件进行下载

  1. 之后将文件解压,放到任意目录,这里将文件放在C盘根目录下:

  1. 进入文件夹,先运行bootstrap.bat

运行结束后会生成 b2.exe可执行文件。

  1. 结束之后开始菜单找到 Visual Studio 2015文件夹,运行目录下面的 VS2015 X86 本机工具命令提示符

  1. 在命令窗口中定位到刚才解压的boost文件夹下,运行如下命令:

b2 -j4 --debug-symbols=on --build-type=complete toolset=msvc-14.0 threading=multi runtime-link=shared address-model=32

需要等待一段时间,即可生成动态链接库

  1. 在VS2015中进行配置

在项目属性中进行设置,右键项目—>属性—>配置属性

a. VC++目录—>库目录 —> 添加路径:C:\boost_1_71_0\stage\lib

之前就是这里没有添加动态链接库所以发生了如下报错:

LNK1104 无法打开文件 libboost_thread-vc140-mt-gd-x32-1_71.lib

b. C/C++ —> 常规 —> 附加包含目录 —> 添加路径: C:\boost_1_71_0

注意: 一定要在项目中添加了 C++ 文件之后,项目属性设置中才会出现 C/C++的选项。

接下来程序就可以正常运行了。

对比程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#include <boost/thread.hpp>
#include <iostream>
void wait(int seconds)
{
boost::this_thread::sleep(boost::posix_time::seconds(seconds));
}

void threadFun()
{
for (int i = 0; i < 5; ++i)
{
wait(1);
std::cout << i << std::endl;
}
}

int main()
{
boost::thread t(threadFun); // 上面的那个函数
t.join(); // join() 方法是一个阻塞调用:它可以暂停当前线程,直到调用 join() 的线程运行结束。
return 0;
}