起初刚学C++时,很不习惯用new,后来看老外的程序,发现几乎都是使用new,想一想区别也不是太大,但是在大一点的项目设计中,有时候不使用new的确会带来很多问题。

当然这都是跟new的用法有关的。new创建类对象,使用完后需使用delete删除,跟申请内存类似。所以,new有时候又不太适合,比如在频繁调用场合,使用局部new类对象就不是个好选择,使用全局类对象或一个经过初始化的全局类指针似乎更加高效。

一、new创建类对象与不new区别

下面是自己总结的一些关于new创建类对象特点:

  • new创建类对象需要指针接收,一处初始化,多处使用
  • new创建类对象使用完需delete销毁
  • new创建对象直接使用堆空间,而局部不用new定义类对象则使用栈空间
  • new对象指针用途广泛,比如作为函数返回值、函数参数等
  • 频繁调用场合并不适合new,就像new申请和释放内存一样

二、new创建类对象实例

1、new创建类对象例子:

CTest* pTest = new CTest();

delete pTest;

pTest用来接收类对象指针。

不用new,直接使用类定义申明:

CTest mTest;

此种创建方式,使用完后不需要手动释放,该类析构函数会自动执行。而new申请的对象,则只有调用到delete时再会执行析构函数,如果程序退出而没有执行delete则会造成内存泄漏。

2、只定义类指针

这跟不用new申明对象有很大区别,类指针可以先行定义,但类指针只是个通用指针,在new之前并为该类对象分配任何内存空间。比如:

CTest* pTest = NULL;

但使用普通方式创建的类对象,在创建之初就已经分配了内存空间。而类指针,如果未经过对象初始化,则不需要delete释放

3、new对象指针作为函数参数和返回值

下面是天缘随手写一个例子,不太严谨。主要示意一下类指针对象作为返回值和参数使用。

 

示例:

类的内存分配简单总结,代码片段如下:

#include <iostream>
using namespace std;

/**
* yanggang
* https://blog.mimvp.com
* 2014-08-11
**/


class ClassA {
	private: 
		int A;
		int B;

    void prin1() {
 	}

	void prin2() {
	}

	virtual void prin3() {
	}
};

class ClassB : public ClassA {
	public:
		int C;
		int D;

	void prin4() {
	}

	void prin5() {
	}

	virtual  void  prin6() {
	}
};

int main(int argc, char* argv[]) {
	cout<<sizeof(ClassA)<<endl;		// 16
	cout<<sizeof(ClassB)<<endl;		// 24

	return 0;
}

测试环境:

Ubuntu 12.04.5 LTS   x86_64 GNU/Linux

g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 编译参数 -m64 

结果是

homer@homer-pc:~/Desktop$ g++ testMem.c -o testMem && ./testMem
16
24

结果为什么是这样?

32位系统int4个字节,64位系统int也占4个字节(不是8个字节),而一个类中所有的虚函数通过一个虚函数指针管理,类对象的大小只包含这个vptr指针(在64位机器上指针sizeof为8个字节),其他虚函数是放在别的内存空间中管理,vptr指针在64位机器上是8个字节大小(32位机器上是4个字节)。注意到普通成员函数并不占类对象的大小空间,因为普通成员函数通过this指针管理,一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。

sizeof(ClassA)

1)int A 和 int B 各占4个字节,考虑64位机器编译器对其规则,合并为8个字节

2)virtual void prin3() 虚函数的vptr指针,在64位机器编译器上占8个字节

3)合计 sizeof(ClassA)为 8 + 8 = 16个字节

this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。这个this指针会因编译器不同而有不同的放置位置可能是栈,也可能是寄存器,甚至全局变量。

子类其实不管如何继承,用sizeof()算该类的大小都会把父类中的私有成员变量所占的空间算进去,也就是说,私有变量也在子类中分配了内存,但你却不可以直接访问,这起到一个保护作用,这如同一个珠宝,共有继承就是开放性的展览,而私有继承是把珠宝锁起来,你却不能动,要动珠宝如果有管家(基类的public中定义了一些对其私有变量操作的成员函数,)只能让管家帮你代劳。

sizeof(ClassB)

1)int A 和 int B 各占4个字节,是父类ClassA中的私有变量,合并占用8个字节

2)virtual void prin3() 虚函数的vptr指针,父类ClassA在子类中不会分配空间

3)int C 和 int D 各占4个字节,在子类中会分配空间,合并占用8个字节

4)virtual void prin6() 虚函数的vptr指针,在64位机器编译器上占8个字节

5)合计 sizeof(ClassB)为 8 + 8 + 8 = 24个字节

注明: 上述示例在32位编译器上测试,sizeof(ClassA)和sizeof(ClassB)分别为12和20字节(虚函数指针占用空间少了4字节)

 

知识延伸:

1) 空 ClassA(验证空Class占用空间大小

class ClassA {
	private: 

    void prin1() {
 	}

	void prin2() {
	}
};

cout<<sizeof(ClassA)<<endl;		// 1

 

2) ClassA 只有一个char(验证编译器对其规则

class ClassA {
	private: 
		char C;

    void prin1() {
 	}

	void prin2() {
	}
};

cout<<sizeof(ClassA)<<endl;		// 1

 

3)ClassA 有一个char和一个虚函数(或一个long型)(验证编译器对其规则二

class ClassA {
	private: 
		char C;

    void prin1() {
 	}

	void prin2() {
	}

	virtual void prin3() {
	}
};

cout<<sizeof(ClassA)<<endl;		// 16

 

4)ClassA 有一个char和一个虚函数(或一个long型),且对调char和虚函数的先后顺序(验证编译器对其规则三

class ClassA {

    void prin1() {
 	}

	void prin2() {
	}

	virtual void prin3() {
	}

	private: 
		char C;
};

cout<<sizeof(ClassA)<<endl;		// 16

 

5)ClassA 有一个char,一个int,一个虚函数(或一个long型)(验证编译器对其规则四

class ClassA {
	private: 
		char C;
		int A;

    void prin1() {
 	}

	void prin2() {
	}

	virtual void prin3() {
	}
};

cout<<sizeof(ClassA)<<endl;		// 16

 

6)ClassA 有一个char,一个int(验证编译器对其规则五

class ClassA {
	private: 
		char C;
		int A;

    void prin1() {
 	}

	void prin2() {
	}
};

cout<<sizeof(ClassA)<<endl;		// 8

 

7)ClassA 只有一个int(验证编译器对其规则六

class ClassA {
	private: 
		int A;

    void prin1() {
 	}

	void prin2() {
	}
};

cout<<sizeof(ClassA)<<endl;		// 4

 

总结如下:

1) 空类的 sizeof 为1个字节

2) 只有一个char的类,sizeof为一个字节

3) 类中含有char和虚函数,将以最大的变量或指针为编译器对齐规则,例如:虚函数指针占8个字节(64位编译器),则char虽然只占1个字节,但对齐后空余了7个字节,合并类占8(指针) + 1(char) + 7(对齐的空字节) = 16个字节

4) 对齐规则,跟变量或虚函数的先后顺序无关,只跟最大变量类型或函数指针有关,函数指针跟编译器最大对齐位数有关(不太好理解,请继续往下看)

5) char和int合占8个字节,虚函数指针占8个字节,且以最大的虚函数指针的8字节对其,其中char占一个空余3个字节合并占4个,int占4个字节,按8位规则对齐,合计16字节

6) 一个char和int合并占8个字节,无虚函数,此时以最大变量类型int对齐,因此char占1字节空3字节占4字节

7)一个int占4位,自己便是最大的对齐规则; 2)一个char同理也占1个字节,此处跟编译器最大64位对齐规则无关(即一个int或一个char,不会拓展到占用8字节)

 

这里,又延伸出了一个很有意思的问题,空类sizeof为什么不为0,而为1?

回答这个问题,需要回到编译器和类的实例化问题上来,在学校看过《深度探索C++对象模型》,类分抽象类和普通类,空类属于普通类,是可以被实例化的。

每个类的实例,在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,因此1)中的空类ClassA默认会占用1个字节