《C++ Primer》学习笔记 — 特殊工具与技术

一、重载new

重载new操作符时,我们必须使用其定位形式。也就是说,我们必须调用重载new操作符的实参是在new和类型名中间传递的:

#include <iostream>
using namespace std;

class CLS_Test
{
public:
	void* operator new(size_t size, const char* _pc);
};

void* CLS_Test::operator new(size_t size, const char* _pc)
{
	cout << "*pc = " << _pc << endl;
	return ::operator new(size);
}

int main()
{
	CLS_Test* pTest = new ("123")CLS_Test;
}

在这里插入图片描述
我们也可以提供一个普通重载函数,其结果与上面一致:

void *operator new(size_t size, const char* _pc)
{
	cout << "*pc = " << _pc << endl;
	return ::operator new(size);
}

需要注意的是,对于这种外部函数,我们无法重载下面这个版本:

void *operator new(size_t, void*)

其余的全局版本new函数一般也不需要重载。一旦我们重载了这些函数,相当于重载了全局new函数。所有的动态空间分配都将调用重载的版本。

二、运行时类型识别

1、typeid运算符

typeid运算符可以作用于任意类型的表达式。其中,顶层const将被忽略。如果表达式是一个引用,则typeid返回该引用所引对象的类型。不过当其作用域数组或函数时,并不会执行向指针的标准类型转换;也就是说,对数组使用typeid运算符的结果讲述数组类型而非指针类型:

#include <iostream>
using namespace std;

string test(int a, char b) {return "";}

int main()
{
	int i = 5;
	const int* pci = &i;
	int* const pi = &i;
	int arr[20];
	cout << "typeid(const int*) " << typeid(pci).name() << endl;
	cout << "typeid(int* const) " << typeid(pi).name() << endl;
	cout << "typeid(int[]) " << typeid(arr).name() << endl;
	cout << "typeid(string test(int, char)) " << typeid(test).name() << endl;
}

在这里插入图片描述
以上变量的类型都是静态类型,因此这些值都是编译时确定的。

2、使用RTTI

在某些情况下RTTI非常有用。例如我们想为具有继承关系的类实现全等于运算符时。很自然的一个想法是使用虚函数,然后将相等的判断委托给该函数。然而,虚函数有一个缺点,那就是其基类和派生类版本必须一致。因此,这个参数只能是基类(引用、指针)的类型。那么此时我们借助RTTI的功能:

class CLS_Base
{
public:
	virtual bool equal(const CLS_Base& other) const 
	{
		...
	}
};

class CLS_Derived : public CLS_Base
{
public:
	virtual bool equal(const CLS_Base& other) const override
	{ 
		auto r = dynamic_cast<const CLS_Derived&>(other);
		...
	}
};

bool operator==(const CLS_Base& left, const CLS_Base& right)
{
	return typeid(left) == typeid(right) && left.equal(right);
}

这样每个类可以处理各自的类型,派生类也可以显式调用基类的equal函数以比较其基类部分。

三、将成员函数用作可调用对象

在许多STL算法中,我们可以传入可调用对象进行特殊处理,如find_if、copy_if等。然而, 成员函数或其指针是无法被直接用作可调用对象的,因为其隐含了一个类对象作为参数。我们有三种方式可以将其转化为可调用对象。

1、function

#include <iostream>
#include <functional>
#include <vector>
using namespace std;

int main()
{
	vector<string> vecStr = { "I", "", "am", "", "here" };
	function<bool(const string&)> fcn = &string::empty;
	auto iter = find_if(vecStr.begin(), vecStr.end(), fcn);
	cout << distance(vecStr.begin(), iter) << endl;
}

在这里插入图片描述
这是通过function模板显式地指出该函数的入参和返回值。

2、mem_fn

mem_fn是一个模板函数,用于生成指定参数的包装调用对象:

auto fcn = mem_fn(string::empty);

使用此函数生成的可调用对象可以通过对象调用,也可以通过指针调用:

string str = "";
fcn(str);
fcn(&str);

我们可以认为其中包含了两个重载函数。

3、bind

using namespace std::placeholders;
auto fcn = bind(&string::empty, _1);

四、union

union是一种特殊的类。其可以有多个数据成员,但是同一时刻只有一个数据成员可以有值。当我们给某个成员赋值后,其他成员就变成未定义的状态了。因此,分配给一个union对象的空间至少要能容纳它的最大的数据成员。
类的某些特性对union同样适用,但并非所有特性都如此、union不能含有引用类型的成员。在C++11中,union可以包含提供了构造和析构函数到的类成员。union还可以为其成员指定访问级别。除此之外,我们可以为union定义构造函数和析构函数。

1、union的定义和使用

在定义union对象时,如果提供了初始值,该初始值将被用于初始化第一个成员。我们可以使用 . 操作符访问 union 的成员。

#include <iostream>
using namespace std;

union myUnion
{
	char ch;
	double db;
};

int main()
{
	myUnion un('c');
	un.db = 100.0;
	cout << sizeof(un) << " " << sizeof(double) << endl;
}

在这里插入图片描述
我们还可以使用匿名union的方式创建对象:

union
{
	char ch;
	double db;
};

这种方式适用于全局或只使用一次的union结构。我们可以在其声明的作用域中直接使用chdb

2、含有类成员的union

对于含有类成员的union,在为其重新赋值时(从类对象转换为内置类型或反之),需要调用该类型的构造或析构函数。与类包含中的成员函数合成不同,对于union,如果其包含的类成员自定了默认构造函数或拷贝控制成员,编译器将为union合成相应的版本并将其声明为删除。因此下面的定义无法编译通过:

union myUnion
{
	string str;
	double db;
};
int main()
{
	string str;
	myUnion un(str);
}

3、使用类管理union

对于union来说,要想构造或销毁类类型的成员必须执行非常复杂的操作。因此我们通常把含有类类型成员的union嵌套在另一个类中。这个类可以管理并控制与union有关的状态转换。为了追踪union中当前存放的值类型,我们通常会定一个一个独立的对象,该对象称为union的判别式:

#include <iostream>
using namespace std;

class CLS_Test
{
public:
	CLS_Test()
	{
		cout << "CLS_Test()" << endl;
	}

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

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

	~CLS_Test()
	{
		cout << "~CLS_Test()" << endl;
	}
};

union myUnion
{
	string str;
	double db;
};

class CLS_Union
{
public:
	CLS_Union() :
		type(DBL),
		db(0.0) 
	{}

	CLS_Union(const CLS_Union& other) :
		type(other.type)
	{
		if (type == CLS)
		{
			cls = other.cls;
		}
		else
		{
			db = other.db;
		}
	}


	CLS_Union operator=(double _db)
	{
		checkType();
		db = _db;
		type = DBL;	
		return *this;
	}

	CLS_Union operator=(const CLS_Test& _cls)
	{
		if (type == CLS)
		{
			cls = _cls;
		}
		else
		{
			new (&cls) CLS_Test(_cls);
		}

		type = CLS;
		return *this;
	}

	~CLS_Union()
	{
		checkType();
	}

private:
	union
	{
		double db;
		CLS_Test cls;
	};

	enum { DBL, CLS } type;

	void checkType()
	{
		if (type == CLS)
		{
			cls.~CLS_Test();
		}
	}
};

int main()
{
	CLS_Union un;
	CLS_Test test;
	un = test;
}

在这里插入图片描述
这里我们需要注意:作为union组成部分的类成员无法自动销毁,因为其不清楚union存储的值是什么类型。我们需要显式地调用析构函数以销毁对象。 因此,如果我们将析构函数中的checkType去掉,其结果将变为:
在这里插入图片描述
这里有两个myUnion中的CLS_Test成员没有释放掉,分别是CLS_Union赋值运算符返回的临时对象中的成员以及直接构造的CLS_Union成员。
在拷贝赋值的时候,如果union中原类型不为类类型,我们需要调用定位new运算符调用构造函数。这是因为相应的内存已经分配好,我们直接在其上进行构造即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值