C ++ Object Construction Semantics

1 Object assignment and destructuring semantics

1.1 Object copy

When we do not write the default copy constructor and do not meet the conditions of the compiler for our default synthesis constructor, when we copy and construct an object, the compiler will also perform some special copy processing

EG:

#include<iostream>
using namespace std;

class A
{
public:
	int a;
};

int main()
{
	A a;
	a.a = 1;
	
	A a1 = a;
	cout << a1.a << endl;  //打印1

	return 0;
}

1.2 Object destruction

In the following two cases, if we do not write the destructor ourselves, the compiler will synthesize the destructor for the object:

  1. There is a destructor in the inherited base class, the compiler will synthesize a destructor for the derived class and call the base class destructor
  2. There is a member variable type of class A (class A has a destructor)

2 New and delete exploration

2.1 The difference between adding no parentheses after new

EG:

#include<iostream>
using namespace std;


class A
{
public:
	int a;
};

class B
{
public:
	B()
	{

	}
	int b;
};

int main()
{

	//对于类A,我们没有自己提供构造函数
	/*
	A a;
	cout << a.a << endl;  //会报错,使用未初始化变量

	A a1 = A();
	cout << a1.a << endl;  //会报错,使用未初始化变量
	*/

	A* a = new A();  //对于没有构造函数的类,在new之后带有括号,类成员变量会被初始化为0
	cout << a->a << endl;

	A* a1 = new A;
	cout << a1->a << endl; //new后面没有加括号,在VS2017中虽然没有报错,但是打印的是一个随机值(由此推断:C++在栈上的变量必须初始化才可以使用,但是堆上的变量可以不用初始化直接使用不会报错)

	//对于类B,由于自己提供了一个无参构造函数,所以以下两种写法是一样的
	B *b = new B();
	B *b1 = new B();
	cout << b->b << endl;  //打印随机值
	cout << b1->b << endl;  //打印随机值

	return 0;
}

2.2 What did new and delete do

For a class with a constructor and a class with a destructor, new and delete mainly do the following things

new: first call operator new, then call the malloc function in this new keyword, and then call the class constructor

delete: first call the destructor of the class, then call operator delete, and then call the free function in the delete keyword

 

When we use the new keyword to apply for a piece of memory, the memory actually consumed by the compiler is larger than the memory we applied for, because the compiler needs a piece of memory to manage the memory we applied for

2.3 Overloading new and delete operators in a class

EG:

#include<iostream>
using namespace std;

class A
{
public:

	//构造函数与析构函数
	A()
	{
		cout << "构造函数" << endl;
	}

	~A()
	{
		cout << "析构函数" << endl;
	}

	//重载new与delete操作符
	void* operator new(size_t size)
	{
		cout << "new" << endl;
		return (A *)malloc(size);
	}

	void operator delete(void *p)
	{
		cout << "delete" << endl;
		free(p);
	}

	//重载new[]与delete[]操作符
	void* operator new[](size_t size)
	{
		cout << "new[]" << endl;
		return (A *)malloc(size);
	}

	void operator delete[](void *p)
	{
		cout << "delete[]" << endl;
		free(p);
	}

};

void func1()  //测试 A类中重载的new与delete操作符
{
	A *a = new A();
	delete a;
}

void func2()
{
	A *a = new A[3]();
	delete[]a;
}

int main()
{

	func1();
	func2();

	return 0;
}

2.4 Overload global new and delete

EG:

#include<iostream>
using namespace std;

void *operator new(size_t size)
{
	cout << "全局new" << endl;
	return malloc(size);
}
void *operator new[](size_t size)
{
	cout << "全局new[]" << endl;
	return malloc(size);
}
void operator delete(void *phead)
{
	cout << "全局delete" << endl;
	free(phead);
}
void operator delete[](void *phead)
{
	cout << "全局delete[]" << endl;
	free(phead);
}


int main()
{
	int *a = new int();

	return 0;
}

2.5 Positioning new

Positioning new: Initialize an object in the allocated memory, use positioning new does not apply for new space

EG:

#include<iostream>
using namespace std;


int main()
{
	void * vp = malloc(sizeof(int));

	int *p = new(vp) int();  //使用定位new

	free(vp);

	return 0;
}

2.6 Overload multiple versions of new

EG:

#include<iostream>
using namespace std;

class A
{
public:

	//重载new关键字第一个参数类型必须是size_t
	void *operator new(size_t size, void *phead)
	{
		cout << "重载定位new" << endl;
		return phead;
	}
	void *operator new(size_t size, int a)
	{
		cout << "重载带一个int参数的船新版本的new" << endl;
		return malloc(size);
	}
	void *operator new(size_t size, int a,int b)
	{
		cout << "重载带两个int参数的船新版本的new" << endl;
		return malloc(size);
	}
};


int main()
{

	void *vp = malloc(sizeof(A));
	A *a1 = new (vp) A();  //使用重载的定位new

	A *a2 = new (3) A();  //使用重载带一个int参数的new

	A *a3 = new (3,5) A();  //使用重载带两个int参数的new

	delete a1;
	delete a2;
	delete a3;

	return 0;
}

 

2.7 Memory Pool

When we frequently use new to apply for space, because new uses malloc internally, and the space consumed by malloc at a time is larger than the space we applied for, if it is frequently applied for small block space, it is even more worthless. The memory pool applies for a larger space at a time, and when the memory needs to be applied, it is taken from the applied larger memory space.

EG:

#include<iostream>
#include <ctime>
using namespace std;

class A
{
public:
	static void *operator new(size_t size);
	static void operator delete(void *phead);
	static int m_iCout; //分配计数统计,每new一次,就统计一次
	static int m_iMallocCount; //每malloc一次,我统计一次
private:
	A *next;
	static A* m_FreePosi; //总是指向一块可以分配出去的内存的首地址
	static int m_sTrunkCout; //一次分配多少倍的该类内存
};

int A::m_iCout = 0;
int A::m_iMallocCount = 0;
A *A::m_FreePosi = nullptr;
int A::m_sTrunkCout = 5; //一次分配5倍的该类内存作为内存池子的大小

void* A::operator new(size_t size)
{
	A *tmplink;
	if (m_FreePosi == nullptr)
	{
		//为空,我要申请内存,要申请一大块内存
		size_t realsize = m_sTrunkCout * size; //申请m_sTrunkCout这么多倍的内存
		m_FreePosi = reinterpret_cast<A*>(new char[realsize]); //传统new,调用的系统底层的malloc
		tmplink = m_FreePosi;

		//把分配出来的这一大块内存(5小块),彼此要链起来,供后续使用
		for (; tmplink != &m_FreePosi[m_sTrunkCout - 1]; ++tmplink)
		{
			tmplink->next = tmplink + 1;
		}
		tmplink->next = nullptr;
		++m_iMallocCount;
	}
	tmplink = m_FreePosi;
	m_FreePosi = m_FreePosi->next;
	++m_iCout;
	return tmplink;
}

void A::operator delete(void *phead)
{
	(static_cast<A*>(phead))->next = m_FreePosi;
	m_FreePosi = static_cast<A*>(phead);
}


int main()
{
	clock_t start, end; //包含头文件 #include <ctime>
	start = clock();
	//for (int i = 0; i < 500'0000; i++)
	for (int i = 0; i < 15; i++)
	{
		A *pa = new A();
		printf("%p\n", pa);
	}
	end = clock();
	cout << "申请分配内存的次数为:" << A::m_iCout << " 实际malloc的次数为:" << A::m_iMallocCount << " 用时(毫秒): " << end - start << endl;
	return 0;
}

2.8 Embedded pointer

In the memory pool, each new object needs to consume four more bytes to store the next pointer, which is a bit wasteful. The introduction of embedded pointers can solve this problem. The embedded pointer uses the first four bytes of the object to store the next pointer, so the embedded pointer must use more than four bytes of memory (the memory occupied by a pointer on the X86 platform is 4 bytes)

EG:

#include<iostream>
using namespace std;

class TestEP
{
public:
	int m_i;
	int m_j;

public:
	struct obj //结构体
	{
		//成员,是个指针
		struct obj *next;  //这个next就是个嵌入式指针
	};
};

int main()
{
	TestEP mytest;
	cout << sizeof(mytest) << endl; //8
	TestEP::obj *ptemp;  //定义一个指针
	ptemp = (TestEP::obj *)&mytest; //把对象mytest首地址给了这个指针ptemp,这个指针ptemp指向对象mytest首地址;
	cout << sizeof(ptemp->next) << endl; //4
	cout << sizeof(TestEP::obj) << endl; //4
	ptemp->next = nullptr;

	return 0;
}

2.9 Memory pool using embedded pointer version

EG:

	//专门的内存池类
	class myallocator //必须保证应用本类的类的sizeof()不少于4字节;否则会崩溃或者报错;
	{
	public:
		//分配内存接口
		void *allocate(size_t size)
		{
			obj *tmplink;
			if (m_FreePosi == nullptr)
			{
				//为空,我要申请内存,要申请一大块内存
				size_t realsize = m_sTrunkCout * size; //申请m_sTrunkCout这么多倍的内存
				m_FreePosi = (obj *)malloc(realsize);
				tmplink = m_FreePosi;

				//把分配出来的这一大块内存(5小块),彼此用链起来,供后续使用
				for (int i = 0; i < m_sTrunkCout - 1; ++i) //0--3
				{
					tmplink->next = (obj *)((char *)tmplink + size);
					tmplink = tmplink->next;
				} //end for
				tmplink->next = nullptr;
			} //end if
			tmplink = m_FreePosi;
			m_FreePosi = m_FreePosi->next;
			return tmplink;
		}
		//释放内存接口
		void deallocate(void *phead)
		{
			((obj *)phead)->next = m_FreePosi;
			m_FreePosi = (obj *)phead;
		}
	private:
		//写在类内的结构,这样只让其在类内使用
		struct obj
		{
			struct obj *next; //这个next就是个嵌入式指针
		};
		int m_sTrunkCout = 5;//一次分配5倍的该类内存作为内存池子的大小
		obj* m_FreePosi = nullptr;
	};

 

3 Temporary objects

3.1 Pit caused by the destruction of temporary objects

If the generated temporary object has no variables, then the generated temporary object will be destroyed after the end of the statement

EG:

#include<iostream>
using namespace std;



int main()
{
	const char *p1 = (string("123") + string("45")).c_str();
	printf("p1 = %s\n", p1);  //由于(string("123") + string("45"))产生的临时对象在执行上行语句后被销毁了,所以打印的结果不是 "12345"

	string str = (string("123") + string("45")).c_str();
	const char *p2 = str.c_str();
	printf("p2 = %s\n", p2);  //打印 "12345"

	return 0;
}

 

Published 123 original articles · praised 31 · 90,000 views +

Guess you like

Origin blog.csdn.net/qq_40794602/article/details/103888828