한국어
C/C++
 

C++ 싱글톤 객체생성 패턴에 대해서

makersweb 2018.07.01 20:15 조회 수 : 4545

싱글톤(Singleton) 은 인스턴스를 생성하는 몇가지 패턴중 하나이다.

프로그램에서 오직 하나의 인스턴스만을 생성할 수 있도록 보장하며 생성된 인스턴스를 전역에서 참조 할 수 있도록 정적 메서드를 제공하는데 보통 getInstance() 또는 instance() 와 같은 이름으로 구현한다.

프로그램에서 오직 하나의 인스턴스만 생성하도록 강제한다는 의미는 객체에서 메모리 할당과 해제등을 제어한다는 것이며 시스템에서 하나의 매니저 역할을 수행하는 객체를 구현할때 유용하게 사용된다.

 

싱글톤 객체는 위에 설명했듯이 정적 메서드를 통해 제공된다. 아래 예제를 살펴보자.

class MySingleton{
public:
  static MySingleton& getInstance();
private:
  MySingleton()= default;
  ~MySingleton()= default;
  MySingleton(const MySingleton&)= delete;
  MySingleton& operator=(const MySingleton&)= delete;
};

 

위의 예제에서 getInstance() 메서스는 static로 선언되어 클라이언트는 오직 이 메스드를 통해서만 이 객체를 참조할 수 있게 된다. 포인터가 아닌 참조를 반환한 것은 클라이언트가 잠재적으로 객체를 제거할 여지가 있기 때문이다.

또한 완전한 싱글톤 객체를 유지하기 위해서 기본 생성자 및 복사생성자, 소멸자등을 private로 선언하였다.

이제 이 클래스에서 제공하는 API를 사용하는 클라이언트는 아래와 같은 방법으로 인스턴스를 참조한다.

MySingleton &myInstance = MySingleton::getInstance();

 

getInstance() 구현

getInstance()를 구현하는 방법은 여러가지가 있는데 그중 C++11에서 가장 일반적이며 또한 심플한 방법으로

Meyers Singeton으로 구현 할 수 있다. 또한 이것은 다중 쓰레드에서 안전한 방법이기도하다.

//Meyers Singleton
MySingleton &MySingleton::getInstance(){
    static MySingleton instance;
    return instance;
}

 

Meyers는 local 정적 변수가 아닌 정적 변수 초기화의 순서는 정해져 있지 않다고 하였는데 즉, 전역 변수로 사용해서 싱글톤 클래스를 초기화 하는 것은 위험하다는 의미이다. 그래서 getInstance()메서드 안에서 정적 변수를 생성하였다.

이제 getInstance()가 처음으로 불려질때 정확히 하나의 객체만 생성된다.

아래의 예제는 여러개의 쓰레드에서 getInstance()를 호출하는 것이다.

#include <iostream>
#include <future>

class MySingleton{
public:
  static MySingleton& getInstance(){
    static MySingleton instance;
    return instance;
  }
private:
  MySingleton()= default;
  ~MySingleton()= default;
  MySingleton(const MySingleton&)= delete;
  MySingleton& operator=(const MySingleton&)= delete;
};

std::chrono::duration<double> getTime(size_t tenMill){

  auto begin= std::chrono::system_clock::now();
  for ( size_t i= 0; i <= tenMill; ++i){
      MySingleton::getInstance();
  }

  return std::chrono::system_clock::now() - begin;
}

int main(){

    auto fut1 = std::async(std::launch::async, getTime, 100000);
    auto fut2 = std::async(std::launch::async, getTime, 10000000);
    auto fut3 = std::async(std::launch::async, getTime, 5000000);

    std::cout << "fur1: " << fut1.get().count() << std::endl;
    std::cout << "fur2: " << fut2.get().count() << std::endl;
    std::cout << "fur3: " << fut3.get().count() << std::endl;
}

 

결과:

case1.png

 

 

쓰레드에 안전한 싱글톤 구현의 다른 방법으로 std::call_once 함수와 std::once_flag 플래그를 이용한 것이다.

std::call_once 함수를 사용하여 정확히 한번 실행 가능한 목록에 등록할 수 있으며 std::once_flag 플래그는 단일 객체가안전하게 초기화됨을 보장한다. 아래는 예제 소스코드이다.

#include <iostream>
#include <future>

class MySingleton{
public:
  static MySingleton& getInstance(){
    std::call_once(initInstanceFlag, &MySingleton::initSingleton);
    return *instance;
  }
private:
  MySingleton()= default;
  ~MySingleton()= default;
  MySingleton(const MySingleton&)= delete;
  MySingleton& operator=(const MySingleton&)= delete;


  static MySingleton* instance;
  static std::once_flag initInstanceFlag;

  static void initSingleton(){
      instance= new MySingleton;
  }
};

MySingleton* MySingleton::instance = nullptr;
std::once_flag MySingleton::initInstanceFlag;

std::chrono::duration<double> getTime(size_t tenMill){

  auto begin= std::chrono::system_clock::now();
  for ( size_t i= 0; i <= tenMill; ++i){
      MySingleton::getInstance();
  }

  return std::chrono::system_clock::now() - begin;
}

int main(){

    auto fut1 = std::async(std::launch::async, getTime, 100000);
    auto fut2 = std::async(std::launch::async, getTime, 10000000);
    auto fut3 = std::async(std::launch::async, getTime, 5000000);

    std::cout << "fur1: " << fut1.get().count() << std::endl;
    std::cout << "fur2: " << fut2.get().count() << std::endl;
    std::cout << "fur3: " << fut3.get().count() << std::endl;
}

 

결과:

case2.png

 

 

이밖에 쓰레드 동기화를 위한 뮤텍스(mutex) 잠금으로 구현하기도 한다. 다만 이방법은 getInstance()가 호출 될때마다 잠금이 발생함으로 성능과 관련한 비용이 든다는 단점이 있다.

 

위의 예제를 통해서도 성능차이를 짐작할 수 있었다. 물론 위의 코드는 최적화 되지 않았으며 실행되는 시스템 환경등의 차이는 감안해야 할것이므로 참고만 하자.

 

참고:

http://www.modernescpp.com/index.php/thread-safe-initialization-of-a-singleton

 

번호 제목 글쓴이 날짜 조회 수
공지 C Programming FAQs(한글번역 pdf문서) makersweb 2014.03.18 27750
22 비트 필드의 크기는 해당 유형의 크기를 초과할 수 없다. makersweb 2023.04.25 1379
21 flexible array member 에 대해서 file makersweb 2020.02.20 2908
20 C++로 플러그인 개발 file makersweb 2019.12.08 2741
19 배열과 포인터, 등가포인터 예제 makersweb 2019.01.16 2005
» 싱글톤 객체생성 패턴에 대해서 file makersweb 2018.07.01 4545
17 함수포인터와 typedef로의 선언 makersweb 2018.02.03 1993
16 예제소스를 통해 리틀엔디안(Little endian)과 빅엔디안(Big endian)의 차이점 알아보기 pjk 2014.05.24 8670
15 switch와 if 중 어느 것이 더 빠른가 file makersweb 2017.01.31 3805
14 구조체 배열을 반환하는 함수를 반환하는 함수를 호출하여 구조체 멤버 배열 요소 접근하기 makersweb 2016.05.22 10871
13 c/c++문자열, 유니코드 관련 함수 makersweb 2015.07.09 6230
12 volatile이 정확히 어떤 의미를 가지는 건가요? pjk 2014.09.30 5506
11 HEX(16진수) to ASCII(아스키) 변환 코드 pjk 2014.08.24 35064
10 strcmp, wcscmp, _tcscmp pjk 2014.07.18 11452
9 클래스 멤버에 대한 액세스 제어 pjk 2014.06.27 5540
8 unsigned형의 모든 상수에는 접미사 ‘U’를 사용하여야 한다. file pjk 2014.05.24 38830
7 void형 포인터 pjk 2014.05.24 6101
6 함수 포인터의 배열 pjk 2014.05.24 8664
5 구조체 메모리 저장방식 #pragma pack file pjk 2014.05.24 5957
4 Bit fields(비트필드) pjk 2014.05.24 4850
3 영어를 C언어로, 또는 C언어를 영어로 변환 해주는 cdecl makersweb 2014.03.19 8739