《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结构。我们可以在其声明的作用域中直接使用ch和db。
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运算符调用构造函数。这是因为相应的内存已经分配好,我们直接在其上进行构造即可。