C++ Primer 学习笔记_33_操作符重载与转换(4)--转换构造函数和类型转换运算符归纳、operator new 和 operator delete 实现一个简单内存泄漏跟踪器

C++ Primer 学习笔记_33_操作符重载与转换(4)--转换构造函数和类型转换运算符归纳、operator new 和 operator delete 实现一个简单内存泄漏跟踪器

一、转换构造函数
可以用单个实参来调用的构造函数定义从形参类型到该类型的一个隐式转换。如下:
class Integral
{
public:
    Integral (int = 0); //转换构造函数
private:
    int real;
};
Integral  A = 1; //调用转换构造函数将1转换为Integral类的对象

转换构造函数需满足以下条件之一:
(1)Integral类的定义和实现中给出了仅包括只有一个int类型参数的构造函数;
(2)Integral类的定义和实现中给出了包含一个int类型参数,且其他参数都有缺省值的构造函数;
(3)Integral类的定义和实现中虽然不包含int类型参数,但包含一个非int类型参数如float类型,此外没有其他参数或其他参数都有缺省值,且int类型参数可隐式转换为float类型参数。
    可以通过将构造函数声明为explicit,来禁止隐式转换。


二、类型转换运算符
    通过转换构造函数可以将一个指定类型的数据转换为类的对象。但是不能反过来将一个类的对象转换成其他类型的数据,例如不能将一个Integral类的对象转换成int类型的数据。为此,C++提供了一个称为类型转换运算符的函数来解决这个转换问题。类型转换函数的作用时将一个类的对象转换成另一个类型的数据。在类中,定义类型转换函数的一般格式为:
class Integral
{
public:
    Integral (int = 0);
    operator int(); //类型转换运算符
private:
    int real;
};
Integral A = 1;  //调用转换构造函数将1转换为Integral类的对象
int i = A;  //调用类型转换运算符函数将A类的对象转换为int类型

定义类型转换函数,
注意以下几点:
(1)转换函数必须是成员函数,不能是友元形式;
(2)转换函数不能指定返回类型,但在函数体内必须用return语句以传值方式返回一个目标类型的变量;
(3)转换函数不能有参数。
例如,Integral向int转换的类型转换函数为:
operator int()
{
    return real;
}
这个类型转换函数的函数名是“operator int",希望转换成的目标类型为int,函数体为“return real“。

【例】
下面哪种情况下,B不能隐式转换为A()?
A、class B: public A {}
B、class A: public B {}
C、class B {operator A();}
D、class A {A(const B&);}
解答:B。因为子类包含了父类的部分,所以子类可以转换为父类,但是相反,父类没有子类额外定义的部分,所以不能转换为子类。

总结:非C++内建型别A和B,在以下几种情况下B能隐式转化为A。
(1)B公有继承自A,可以是间接继承的。
class B: public A {};

(2)B中有类型转换函数。
class B
{
    operator A();
};
此时若有“A a;   B b;",则“a = b;“合法

(3)A实现了非explicit的参数为B的构造函数(可以有其他带默认值的参数)
class A
{
    A (const B&);
};
此时若有“A a;   B b;",则“a = b;“合法


三、operator new 和 operator delete的重载

    实际上,我们不能重定义new和delete表达式的行为,能够被重载的是全局函数operator new和operator delete。

    new的执行过程是:首先,调用名为operator new的标准库函数,分配足够大的原始未类型化的内存,以保存指定类型的一个对象;接下来,运行该类型的一个构造函数,用指定初始化式构造对象;最后,返回指向新分配并构造的对象的指针。

    delete的执行过程是:首先,对sp指向的对象运行适当的析构函数;然后通过调用名为operator delete的标准函数释放该对象所用内存。

    以上的opeartor new与operator delete是可以被重载的。

【例1】

下述代码的输出结果是什么?

#include <iostream>
using namespace std;

class X
{
public:
    X() {cout << "constructor" << endl;}
    static void* operator new(size_t size)
    {
        cout << "new" << endl;
        return ::operator new(size);
    }
    static void operator delete(void* point)
    {
        cout << "delete" << endl;
        ::operator delete(point);
    }
    ~X() {cout << "destructor" << endl;}
};

int main()
{
    X* p = new X();
    delete p;
    return 0;
}

解答:

new
constructor
destructor
delete


new operator的特点是:

(1)调用operator new分配足够的空间,并调用相关对象的构造函数; 

(2)不可以被重载

operator new的特点是:

(1)只分配所要求的空间,并调用相关对象的构造函数;

(2)可以被重载

(3)重载时,返回类型必须声明为void*;

(4)重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t;

(5)重载时,可以带其他参数。

    operator new与operator delete和C语言中的malloc与free对应,只负责分配及释放空间。但使用operator new分配的空间必须使用operator delete来释放,而不能使用free,因为他们对内存使用的登记方式不同。反过来也是一样。


1、new有三种用法
(1)new operator
(2)operator new
(3)placement new
    在已经存在的内存上构建对象。

(4)示例

void* operator new(size_t size)
void operator delete(void* p)
void operator delete(void* p, size_t size)
void* operator new(size_t size, const char* file, long line)
void operator delete(void* p, const char* file, long line)

void* operator new[](size_t size)
void operator delete[](void* p)
void operator delete[](void* p, size_t size)
【例2】
//03.cpp
#include <iostream>
using namespace std;
class Test
{
public:
     Test(int n) : n_(n)
     {
          cout<<"Test(int n) : n_(n)"<<endl;
     }
     Test(const Test& other)
     {
          cout<<"Test(const Test& other)"<<endl;
     }
     ~Test()
     {
          cout<<"~Test()"<<endl;
     }
     void* operator new(size_t size)
     {
          cout<<"void* operator new(size_t size)"<<endl;
          void* p = malloc(size);
          return p;
     }
     void operator delete(void* p)  //优先执行
     {
          cout<<"void operator delete(void* p)"<<endl;
          free(p);
     }
     void operator delete(void* p, size_t size)  //可以替代,并和上一个delete共存,但优先执行上一个delete
     {
          cout<<"void operator delete(void* p, size_t size)"<<endl;
          free(p);
     }
     void* operator new(size_t size, const char* file, long line)
     {
          cout<<file<<":"<<line<<endl;  //打印哪个文件哪一行
          void* p = malloc(size);
          return p;
     }
     void operator delete(void* p, const char* file, long line)
     {
          cout<<file<<":"<<line<<endl;
          free(p);
     }
     void operator delete(void* p, size_t size, const char* file, long line)
     {
          cout<<file<<":"<<line<<endl;
          free(p);
     }
     void* operator new(size_t size, void* p) //重载局部的 placement new
     {
          return p;
     }
     void operator delete(void *, void *)  //重载局部的 placement delete
     {
     }
     int n_;
};
void* operator new(size_t size)  //全局的operator new
{
     cout<<"global void* operator new(size_t size)"<<endl;
     void* p = malloc(size);
     return p;
}
void operator delete(void* p)
{
     cout<<"global void operator delete(void* p)"<<endl;
     free(p);
}
void* operator new[](size_t size)
{
     cout<<"global void* operator new[](size_t size)"<<endl;
     void* p = malloc(size);
     return p;
}
void operator delete[](void* p)
{
     cout<<"global void operator delete[](void* p)"<<endl;
     free(p);
}
int main(void)
{
     Test* p1 = new Test(100);	// new operator = operator new + 构造函数的调用
     delete p1;
     char *str1 = new char;  //全局的operator new
     delete[] str1;

     char* str2 = new char[100];  //全局的operator new[]
     delete[] str2;
     char chunk[10];
 
     Test* p2 = new (chunk) Test(200);	//operator new(size_t, void *_Where)
                                       // placement new,不分配内存 + 构造函数的调用
     cout<<p2->n_<<endl;
     p2->~Test();	 //由于p2在placement new中不析构,必须显式调用析构函数
     //Test* p3 = (Test*)chunk;
     Test* p3 = reinterpret_cast<Test*>(chunk);
     cout<<p3->n_<<endl;
     #define new new(__FILE__, __LINE__)
     //Test* p4 = new(__FILE__, __LINE__) Test(300);  //通过这种方式,容易找到内存泄露的地方
     Test* p4 = new Test(300);
     delete p4;
     return 0;
}
运行结果:
void* operator new(size_t size)
Test(int n) : n_(n)
~Test()
void operator delete(void* p)
global void* operator new(size_t size)
global void operator delete(void* p)
global void* operator new[](size_t size)
global void operator delete[](void* p)
Test(int n) : n_(n)
200
~Test()
200
d:\cpp\03.cpp:116
Test(int n) : n_(n)
~Test()
void operator delete(void* p)

从输出可以看出几点:

(1)new operator 是分配内存(调用operator new) + 调用构造函数

(2)operator new 是只分配内存,不调用构造函数

(3)placement new 是不分配内存(调用operator new(与2是不同的函数) 返回已分配的内存地址),调用构造函数

(4)delete 是先调用析构函数,再调用operator delete.

(5)如果new 的是数组,对应地也需要delete [] 释放



【例3】
如何限制栈对象的生成?如何限制堆对象的生成?
解答:
禁止产生堆对象:
    产生堆对象的唯一方法是使用new操作,通过禁止使用new就可以禁止产生堆对象。如何禁止new操作呢?new操作执行时会调用operator new,而operator new是可以重载的。因此,就是使operator new为private,为了对称,最好将operator delete也重载为private。
禁止产生栈对象:
    创建栈对象不需要调用new,创建栈对象时会移动栈顶指针以“挪出”适当大小的空间,然后在这个空间上直接调用对应的构造函数以形成一个栈对象,而当函数返回时,会调用其析构函数释放这个对象,然后再调整栈顶指针回收那块栈内存。因此,将构造函数和析构函数设为私有,这样系统就不能调用构造函数和析构函数了,当然就不能在栈中生成对象。




参考:

C++ primer 第四版

你可能感兴趣的:(C++,C++,Primer,操作符重载与转换)