C++ 특수 클래스 디자인

힙에만 객체를 생성할 수 있는 클래스 설계

힙에만 객체를 생성할 수 있는 경우 스택과 정적 영역에 객체를 생성하는 것으로 제한됩니다. 단계는 다음과 같습니다.

방법 1: 생성자를 비공개로 설정합니다.

  • 사용자가 생성자를 직접 호출하여 스택이나 정적 영역에 객체를 생성하지 못하도록 생성자를 비공개로 만드십시오.
  • public 영역에 static 멤버 함수를 제공하고, static 멤버 함수에서 new를 사용하여 객체를 생성하는데, 이때 new는 멤버 함수에 있으므로 private 영역의 생성자를 호출할 수 있다.
  • 복사 생성자의 외부 사용을 제한하여 스택에 개체를 생성하는 것을 제한하려면 C++11의 delete 키워드를 사용하여 기본적으로 복사 생성자를 자동으로 생성하도록 컴파일러를 제한할 수 있습니다.
class HeapOnly
{
    
    
public:
	//方法一:提供一个获取对象的接口,并且该接口必须设置为静态成员函数
	static HeapOnly* CreateObj()
	{
    
    
		return new HeapOnly;
	}
	void Delete()
	{
    
    
		cout << "Delete()" << endl;
		delete this;
	}
	//限制使用拷贝构造,赋值拷贝.
	HeapOnly(const HeapOnly& hp) = delete;
	HeapOnly& operator=(const HeapOnly& hp) = delete;
private:
	HeapOnly()
		:_a(0)
	{
    
    }
	int _a;
	//C++11
	//HeapOnly(const HeapOnly&) = delete;
};
int main()
{
    
    
	//限制在栈上创建.
	/*HeapOnly hp1;
	
	//限制在静态区创建.
	static HeapOnly hp2;*/

	HeapOnly* ptr = HeapOnly::CreateObj();

   ptr->Delete();
}

참고 :

  • 우리가 제공하는 CreateObj() 함수는 비정적 멤버는 객체를 통해 호출해야 하고, 객체는 CreateObj() 함수를 통해 생성해야 하므로 정적 함수로 설정해야 합니다.(다음 작성도 비슷합니다.)

방법 2: 소멸자를 비공개로 설정

이 메서드는 두 가지 유형으로 나뉩니다 :
1. 정적 멤버 함수를 사용하여 소멸자를 호출합니다.
2: Delete() 함수를 공개적으로 작성하고 this 포인터를 사용하여 소멸시킵니다.

class HeapOnly
{
    
    
public:
	static void Delete(HeapOnly* ptr)
	{
    
    
		cout << "static Delete " << endl;
		delete ptr;
	}
	void Delete()
	{
    
    
		cout << "Delete()" << endl;
		delete this;
	}
private:
	~HeapOnly()
	{
    
    }
	int _a;
};
int main()
{
    
    
	//HeapOnly hp1;
	
	HeapOnly* ptr = new HeapOnly;
    //1.
    HeapOnly::Delete(ptr);
    //2.
	ptr->Delete();
}

스택에서만 객체를 생성할 수 있는 클래스 설계

객체는 스택에서만 생성할 수 있습니다.즉, 객체는 정적 영역과 힙으로 제한됩니다.아이디어는 이전 디자인과 동일합니다.

  • 힙 또는 정적 영역에서 생성자를 직접 호출하여 개체를 생성하려면 생성자를 private로 설정합니다.
  • 클래스의 생성자는 클래스 내부의 private 영역에 제한을 받지 않고 접근이 가능하므로 스택 영역의 형태로 객체가 생성되고 반환되는 public 영역에 static 멤버 함수를 제공한다.

class StackOnly
{
    
    
public:
	//2、提供一个获取对象的接口,并且该接口必须设置为静态成员函数
	static StackOnly CreateObj()
	{
    
    
		StackOnly st;
		return st;
     }
	//不可以限制拷贝构造.
	//StackOnly(const StackOnly& st) = delete;
	//StackOnly& operator=(const StackOnly& st) = delete;
private:
	//1、将构造函数设置为私有
	StackOnly()
		:_a(0)
	{
    
    }
	int _a;
};

이 방법의 단점은 개체를 만들기 위해 컴파일러에서 생성한 기본 복사 생성자에 대한 외부 호출을 방지할 수 없다는 것입니다.

StackOnly st1 = StackOnly::CreateObj();
static StackOnly st2(st1); //在静态区中拷贝构造对象st1
StackOnly* ptr = new StackOnly(st1); //在堆上拷贝构造对象st1

참고 :

  • CreateObj 함수에서 생성된 객체는 임시 객체이므로 값으로 반환해야 하므로 복사 생성을 제한할 수 없으며 값에 의한 복사는 반드시 복사 생성을 호출합니다.

힙에 있는 복사 구성 객체의 경우 new를 제한하는 방법을 사용할 수 있습니다.

class StackOnly
{
    
    
public:
	//2、提供一个获取对象的接口,并且该接口必须设置为静态成员函数
	static StackOnly CreateObj()
	{
    
    
		StackOnly st;
		return st;
     }
     //限制nwe.
    void* operator new(size_t n) = delete;
private:
	//1、将构造函数设置为私有
	StackOnly()
		:_a(0)
	{
    
    }
	int _a;
};

설명:
new는 객체를 생성하기 때문에 주로 두 단계로 나뉩니다:
1. operator new 함수를 호출하여 공간을 생성합니다
2. 생성자를 호출하여 객체 초기화를 완료합니다.

위의 설명을 바탕으로 new 연산자의 사용을 제한하는 연산자 new를 삭제한 다음 힙에 객체를 생성하기 위한 복사 구문의 사용을 제한하지만 정적 영역에서 객체를 생성하기 위해 복사 구문을 사용하는 방법은 여전히 ​​해결되지 않습니다. (일반적으로 실제로는 거의 발생하지 않음).

복사할 수 없는 클래스 디자인

클래스가 복사를 금지하도록 하려면 클래스가 복사 생성 및 할당 연산자 오버로딩을 호출할 수 없도록 만들기만 하면 됩니다.

  • C++98에서 복사 생성자를 할당으로 오버로드하면 정의가 선언되지 않고 액세스 권한이 비공개로 설정됩니다.
  • C++11에서는 일반적으로 =delete를 따라잡기 위해 기본 멤버 함수에 추가되는데, 이는 컴파일러가 기본 멤버 함수를 삭제할 수 있음을 의미합니다.
class CopyBan
{
    
    
public:
	CopyBan()
	{
    
    }
private:
	//C++98
	CopyBan(const CopyBan&);
	CopyBan& operator=(const CopyBan&);
	//C++11
	//CopyBan(const CopyBan&) = delete;
	//CopyBan& operator=(const CopyBan&) = delete;
};

상속할 수 없는 클래스 설계

C++98

C++98에서 이 클래스의 생성자는 비공개로 설정됩니다. 파생 클래스가 기본 클래스의 전용 멤버를 상속하는 경우 기본 클래스의 생성자를 호출하여 기본 클래스의 전용 멤버를 초기화해야 함을 의미하기 때문입니다. 클래스는 기본 클래스이지만 클래스의 생성자는 비공개로 설정되어 있습니다. 즉, 파생 클래스는 기본 클래스의 생성자에 액세스할 수 없으므로 클래스를 상속받은 후에 개체를 만들 수 없습니다.

class NonInherit
{
    
    
public:
	static NonInherit CreateObj()
	{
    
    
		return NonInherit();
	}
private:
	//将构造函数设置为私有
	NonInherit()
	{
    
    }
};

C++11

기본 클래스의 생성자는 C++98에서 전용으로 설정되어 있으므로 파생 클래스에서 상속할 수 없습니다. 실제로 여전히 성공적으로 컴파일할 수 있으며 파생 클래스를 인스턴스화할 수 없습니다. 따라서 final 키워드는 C++11에 추가된 클래스를 상속할 수 없다는 것을 나타내기 위해 클래스를 수정하기 위해 final을 사용하고, 강제로 상속되면 성공적으로 컴파일할 수 없습니다.`

//基类
class Base  final
{
    
    
private:
	Base()
		:_a(0)
	{
    
    }
protected:
	int _a;
};
//派生类
class Derived : protected Base
{
    
    
public:
//
protected:
	int _a;
	int _b;
};

하나의 객체만 생성할 수 있는 클래스 설계(싱글톤 모드)

싱글톤 모드: 클래스는 하나의 객체만 생성할 수 있습니다. 즉, 싱글톤 모드는 시스템에 클래스의 인스턴스가 하나만 있는지 확인하고 액세스할 수 있는 전역 액세스 지점을 제공하며 모든 프로그램 모듈에서 공유합니다. . 예를 들어, 서버 프로그램에서 서버의 구성 정보는 파일에 저장되고 이러한 구성 데이터는 싱글톤 객체에 의해 균일하게 읽혀지고 서비스 프로세스의 다른 객체는 이 싱글톤 객체를 통해 구성 정보를 얻습니다. 이 접근 방식은 복잡한 환경에서 구성 관리를 단순화합니다.

싱글톤 모드에는 두 가지 구현 모드가 있습니다 .

배고픈 남자 모드

배고픈 모드는 사용자가 향후 사용 여부에 관계없이 프로그램이 시작될 때 고유한 인스턴스 개체가 생성됩니다. 간단히 말해서 개체는 기본 기능보다 먼저 생성됩니다.

배고픈 사람 모드의 구현 단계는 다음과 같습니다 .

  • 생성자를 비공개로 설정하고 복사 생성 및 연산자 오버로드를 삭제하여 복사 생성을 외부에서 사용하여 객체를 생성하지 못하도록 합니다.
  • 싱글톤 객체에 대한 정적 포인터를 제공하고 메인 함수 전에 싱글톤 객체의 초기화를 완료합니다.
  • 정적 멤버 함수를 사용하여 전역 액세스 지점이라고도 하는 싱글톤 객체를 얻습니다.
 class Singleton
{
    
    
public:
	static Singleton* GetInstance()
	{
    
    
		return _pList;
	}
	void* Aoll()
	{
    
    
		cout << "Aoll" << endl;
		return _pinst;
	}
	void Dell(void* ptr)
	{
    
    
		cout << "Dell" << endl;
	}
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	MemoryPool()
	{
    
    //}
	//静态成员.
	static MemoryPool* _pinst;
};
//类外定义
MemoryPool* MemoryPool::_pinst = new MemoryPool;

int main()
{
    
    
	MemoryPool* ptr1 = MemoryPool::GetInstance();
    //通过对象指针调用函数.
	ptr1->Dell(ptr1);
}

참고 :
정적 멤버를 정의할 때 메인 함수보다 먼저 정의하므로 클래스 내의 정적 멤버 함수를 통해 클래스의 유일한 인스턴스 객체를 얻어야 합니다.

장점 : 간단하고 스레드 안전 문제가 없습니다.

단점 :

  1. 프로그램에는 여러 개의 싱글톤이 있고 초기화 순서에 대한 요구 사항이 있습니다.정적 멤버가 먼저 초기화되고 누가 나중에 초기화될 수 있기 때문에 기아 모드를 제어할 수 없습니다.예를 들어, 두 개의 싱글톤 클래스 A와 프로그램에서 B는 요구 사항이 있다고 가정할 때 A가 먼저 초기화를 생성하고 B가 초기화를 다시 생성합니다.

  2. 배고픈 싱글톤 클래스는 초기화 중에 프로그램 시작 속도에 영향을 미치는 많은 작업이 있습니다.

게으른 모드

지연 모드는 인스턴스 객체가 처음 사용될 때만 객체를 생성한다는 의미입니다.

게으른 사람 모드의 구현 단계는 다음과 같습니다 .

  • 생성자를 비공개로 설정하고 복사 생성 및 연산자 오버로드를 삭제하여 복사 생성을 외부에서 사용하여 객체를 생성하지 못하도록 합니다.

  • 싱글톤 객체에 대한 정적 포인터를 제공하고 프로그램 시작 전에 비어 있도록 초기화합니다.

  • 정적 멤버 함수를 사용하여 글로벌 액세스 포인트라고도 하는 싱글톤 개체를 얻습니다. 정적 포인터가 비어 있으면 인스턴스 개체를 처음으로 다시 만들어야 함을 의미합니다. 비어 있지 않으면 원래 인스턴스 개체는 여전히 반환됩니다.

class Singleton
{
    
    
public:
	//da
	static Singleton* GetInstance()
	{
    
    
		if (_pList == nullptr)
		{
    
    
			return _pList = new  Singleton;
		}
		return _pList;
	}
	void* Aoll()
	{
    
    
		cout << "Aoll" << endl;
		return _pList;
	}
	void Dell(void* ptr)
	{
    
    
		cout << "Dell" << endl;
	}
private:
	Singleton()
	{
    
    
	}
	static  Singleton* _pList;
};
Singleton* Singleton::_pList = nullptr;

장점 :

  • 초기화 순서를 제어할 수 있습니다.예를 들어 클래스에 지연 모드의 기본 제공 멤버가 여러 개 있는 경우 먼저 기본 제공 멤버 _a에서 getinstance를 호출한 다음 기본 제공 멤버 _b에서 getInstance를 호출할 수 있습니다. , _a가 먼저 초기화되고 _b가 나중에 초기화됩니다.

  • 기본 제공 멤버는 나중에 초기화되기 때문에 프로그램 시작 속도에는 영향을 미치지 않습니다.

단점 :

  • 상대적으로 복잡합니다(스레드 안전 문제). 이에 대해서는 나중에 자세히 살펴보겠습니다.

싱글톤 패턴 객체 해제 문제

1: 정상적인 상황에서는 싱글톤 개체를 해제할 필요가 없으며 전체 프로그램 작업 중에 사용됩니다.

2: 프로세스가 정상적으로 종료된 후 싱글톤 개체도 리소스를 해제합니다(프로그램 오류로 인해 좀비 프로세스가 되지 않는 한).

3: 싱글톤 개체가 소멸되는 경우와 같은 일부 특수 시나리오를 해제해야 하며 일부 지속성 작업이 필요합니다(파일에 데이터 쓰기, 데이터베이스에 쓰기).

4: GetInstance()를 통해 인스턴스 개체 포인터를 직접 가져온 다음 삭제를 사용하여 삭제하면 보기 흉할 뿐만 아니라 무시하기 쉽습니다. 다음 단계에서는 외부에서 GetInstance(를 호출할지 여부를 확인할 수 없습니다. ) 포인터를 가져옵니다.

따라서 싱글톤 객체의 경우 프로그램이 종료될 때 관련 조건을 자동으로 소멸시키기 위해 임베디드 클래스의 형태를 사용하는 경우가 많습니다.

//垃圾回收类
class CGarbo
{
    
    
public:
	~CGarbo()
	{
    
    
		if (_inst != nullptr)
		{
    
    
			delete _inst;
			_inst = nullptr;
		}
	}
};
//定义一个静态成员变量,在程序结束时,系统会自动调用它的析构函数,从而释放单例对象.
static CGarbo Garbo;

참고 :
포함된 클래스의 형태로 싱글톤 클래스를 파괴해야 하며 리소스를 해제하기 위해 싱글톤 클래스의 소멸자에서 delete를 사용할 수 없습니다. 리소스를 해제하는 데 사용되므로 재귀적으로 삭제합니다.

Supongo que te gusta

Origin blog.csdn.net/m0_63300413/article/details/131284302
Recomendado
Clasificación