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

C++ Primer 学习笔记_29_操作符重载与转换(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,因为他们对内存使用的登记方式不同。反过来也是一样。

【例2】

#include <iostream>
#include <stdlib.h>
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) //与下面的operator delete函数类似,共存的话优先;
    {
        //匹配上面的operator new 函数
        cout << "void operator delete(void* p)" << endl;
        free(p);
    }

    void operator delete(void *p, size_t size)
    {
        cout << "void operator delete(void* p, size_t size)" << endl;
        free(p);
    }
    /**********************************************************************/

    void *operator new(size_t size, const char *file, long line)
    {
        cout << "   void* operator new(size_t size, const char* file, long line);" << endl;
        cout << file << ":" << line << endl;
        void *p = malloc(size);
        return p;
    }

    void operator delete(void *p, const char *file, long line)
    {
        cout << "   void operator delete(void* p, const char* file, long line);" << endl;
        cout << file << ":" << line << endl;
        free(p);
    }

    void operator delete(void *p, size_t size, const char *file, long line)
    {
        cout << "void operator delete(void* p, size_t size, const char* file, long line);" << endl;
        cout << file << ":" << line << endl;
        free(p);
    }
    /**************************************************************************/
    void *operator new(size_t size, void *p)
    {
        cout << "void* operator new(size_t size, void* p);" << endl;
        return p;
    }

    void operator delete(void *, void *)
    {
        cout << "void operator delete(void *, void *);" << endl;
    }
    /**************************************************************************/
    int n_;
};

/*************** global **********************************************/

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);
}
/**********************************************************************/

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;
    delete str1;

    char *str2 = new char[100];
    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();                        // 显式调用析构函数
    //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)

void* operator new(size_t size, void* p);

Test(int n) : n_(n)

200

~Test()

200

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

test.cpp:131

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 第四版

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-07-30 20:31:47

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

C++ Primer 学习笔记_26_操作符重载与转换(1)--可重载/不可重载的操作符、成员函数方式重载、友元函数方式重载

C++ Primer 学习笔记_26_操作符重载与转换(1)--可重载/不可重载的操作符.成员函数方式重载.友元函数方式重载 引言: 明智地使用操作符重载可以使类类型的使用像内置类型一样直观! 一.重载的操作符名 像任何其他函数一样,操作符重载函数有一个返回值和一个形参表.形参表必须具有操作符数目相同的形参.比如赋值时二元运算,所以该操作符函数有两个参数:第一个形参对应着左操作数,第二个形参对应右操作数. 大多数操作符可以定义为成员函数或非成员函数.当操作符为成员函数时,它的第一个操作数隐式绑定

C++ Primer 学习笔记_27_操作符重载与转换(2)--++/--运算符重载、!运算符重载、赋值运算符重载 、String类([]、 +、 += 运算符重载)、&gt;&gt;和&lt;&lt;运算符重载

C++ Primer 学习笔记_27_操作符重载与转换(2)--++/--运算符重载.!运算符重载.赋值运算符重载 .String类([]. +. += 运算符重载).>>和<<运算符重载 一.++/--运算符重载 1.前置++运算符重载 成员函数的方式重载,原型为: 函数类型 & operator++(); 友元函数的方式重载,原型为: friend 函数类型 & operator++(类类型 &); 2.后置++运算符重载 成员函数的方式重载,原型为:

C++ Primer 学习笔记_28_操作符重载与转换(3)--成员函数的重载、覆盖与隐藏、类型转换运算符、*运算符重载、-&gt;运算符重载

C++ Primer 学习笔记_28_操作符重载与转换(3)--成员函数的重载.覆盖与隐藏.类型转换运算符.*运算符重载.->运算符重载 一.成员函数的重载.覆盖与隐藏 对于类层次的同名成员函数来说,有三种关系:重载.覆盖和隐藏,理清3种关系,有助于写出高质量的代码. 1.成员函数的重载 重载的概念相对简单,只有在同一类定义中的同名成员函数才存在重载关系,主要特点时函数的参数类型和数目有所不同:但不能出现函数参数的个数和类型均相同,仅仅依靠返回值类型不同来区分的函数,这和普通函数的重载是完全一致

C++ Primer 学习笔记_64_重载操作符与转换 --转换与类类型【下】

重载操作符与转换 --转换与类类型[下] 四.重载确定和类的实参 在需要转换函数的实参时,编译器自动应用类的转换操作符或构造函数.因此,应该在函数确定期间考虑类转换操作符.函数重载确定由三步组成: 1)确定候选函数集合:这些是与被调用函数同名的函数. 2)选择可行的函数:这些是形参数目和类型与函数调用中的实参相匹配的候选函数.选择可行函数时,如果有转换操作,编译器还要确定需要哪个转换操作来匹配每个形参. 3)选择最佳匹配的函数.为了确定最佳匹配,对将实参转换为对应形参所需的类型转换进行分类.对于

C++ Primer 学习笔记_58_重载操作符与转换 --重载操作符的定义

重载操作符与转换 --重载操作符的定义 引言: 明智地使用操作符重载可以使类类型的使用像内置类型一样直观! 重载操作符的定义 重载操作符是具有特殊名称的函数:保留字operator后接定义的操作符符号.如: Sales_item operator+(const Sales_item &,const Sales_item &); 除了函数调用操作符之外,重载操作符的形参数目(包括成员函数的隐式this指针)与操作符的操作数数目相同.函数调用操作符可以接受任意数目的操作数. 1.重载的操作符名

C++ Primer 学习笔记_59_重载操作符与转换 --输入/输出、算术/关系操作符

重载操作符与转换 --输入/输出.算术/关系操作符 支持I/O操作的类所提供的I/O操作接口,一般应该与标准库iostream为内置类型定义的接口相同,因此,许多类都需要重载输入和输出操作符. 一.输出操作符<<的重载 为了与IO标准库一致,操作符应接受ostream&作为第一个形参,对类类型const对象的引用作为第二个形参,并返回ostream形参的引用! ostream &operator<<(ostream &os,const ClassType &

C++ Primer 学习笔记_63_重载操作符与转换 --转换与类类型【上】

重载操作符与转换 --转换与类类型[上] 引言: 在前面我们提到过:可以用一个实参调用的非explicit构造函数定义一个隐式转换.当提供了实参类型的对象需要一个类类型的对象时,编译器将使用该转换.于是:这种构造函数定义了到类类型的转换. 除了定义到类类型的转换之外,还可以定义从类类型到其他类型的转换.即:我们可以定义转换操作符,给定类类型的对象,该操作符将产生其他类型的对象.和其他转换一样,编译器将自动应用这个转换. 一.转换为什么有用? 定义一个SmallInt的类,该类实现安全小整数,这个

C++ Primer 学习笔记_54_类与数据抽象 --复制构造函数、赋值操作符

复制控制 --复制构造函数.赋值操作符 引言: 当定义一个新类型时,需要显式或隐式地指定复制.赋值和撤销该类型的对象时会发生什么– 复制构造函数.赋值操作符和析构函数的作用!      复制构造函数:具有单个形参,该形参(常用const修饰)是对该类类型的引用.当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式的使用复制构造函数:当将该类型的对象传递给函数或者从函数返回该类型的对象时,将隐式使用复制构造函数.     析构函数:作为构造函数的互补,当对象超出作用域或动态分配的对象被删除

C++ Primer 学习笔记_86_模板与泛型编程 --重载与函数模板

模板与泛型编程 --重载与函数模板 引言: 函数模板可以重载:可以定义有相同名字但参数数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数. 但是,声明一组重载函数模板不保证可以成功调用它们,重载的函数模板可能会导致二义性. 一.函数匹配与函数模板 如果重载函数中既有普通函数又有函数模板,确定函数调用的步骤如下: 1.为这个函数名建立候选函数集合,包括: a.与被调用函数名字相同的任意普通函数. b.任意函数模板实例化,在其中,模板实参推断发现了与调用中所用函数实参相匹配