통합검색
· 마을서비스란?  · 포럼마을  · 일반마을  · 테마마을  · 마을랭킹  · 활동왕
· 덱스퍼트란?  · TECBOX   · PRSBOX   · 이용안내  
· DEXT제품군  · 솔루션베이  · S/W & ESD 컴포넌트
· 프로그램베이
· LiveSeminar  · LiveConference
Visual C++ 포럼마을 입니다.
  마을등급 Visual C++   이 마을은 포럼마을 입니다이 마을은 자유가입제 마을 입니다 마을소개 페이지로 이동 전입신청
마을촌장촌장 나성훈 주민 34036 since 2006-12-29
우리마을 공지사항
질문&답변
강좌&팁
자유게시판
자료실
앨범
개인게시판
마을 게시판
등록된 마을 게시판이
없습니다.
랑데브 게시판
칼럼 게시판
개발자 고충상담
Dev Talk
자유토론방
벼룩시장
재나미 우스개
구인/프로젝트 정보
사람인 채용 게시판
  고객지원 게시판
마이 데브피아
 나의 e-Money 내역
 활동왕 My Page
 스크랩한 게시글보기
 쪽지관리
 주소록관리

 강좌&팁
 다시 보는 GoF 디자인 패턴(싱글톤, 데코레이터)   | VC++ 일반 2018-03-31 오후 8:58:22
 kyh2984@hotmail.com  kyh2984@hotmail.com님께 메시지 보내기kyh2984@hotmail.com님을 내 주소록에 추가합니다.kyh2984@hotmail.com님의 개인게시판 가기 번호: 8877 추천:0  / 읽음:2,261

최신 개발 동향으로 다시 보는 GoF 디자인 패턴

 

1994년에 출간된 GoF 디자인 패턴은 지금까지도 여전히 개발자들에게 혜안을 주고 있다. 하지만 20여년의 세월이 지나면서 애플리케이션은 대형화 되고 처리되는 데이터양도 매우 커졌다, 이런 환경에 맞게 C++ 문법 및 라이브러리 또한 많은 발전이 있었다. GoF 디자인 패턴 중 지면상 두 개의 패턴인 싱글톤과 데코레이터 패턴을 현대적 감각의 C++ 코드로 바꾸어 다시 구현해 보고, 반대로 익숙한 디자인 패턴의 모습을 보며 현재의 C++ 모습을 살펴보도록 하자.

 

평면에 효율성과 안정성 있는 형태를 만들고 그것을 벽면으로 건물을 지으면 어떤 모습일까? 자연 속에서 해답을 찾는다면 벌들이 짓는 집을 생각해 볼 수 있다. 벌집은 꼭짓점과 변이 각각 6개씩 있는 육각형의 문양이 반복적으로 나타난다. 만일 벌집이 정삼각형으로 되어 있다면 벌들이 드나드는데 입구가 좁고 많은 재료가 필요하게 되고, 정사각형으로 만들었다면 누르는 힘에 의해 쉽게 주저앉아 버릴 것이다. 벌들은 거의 오차 없이 두께 0.073, 지름 5.5미리의 13도 각도의 육각형 형태의 벌집을 만들면서 최소한의 재료로 최대한의 공간을 확보하면서 부하를 잘 버티는 경제적인 구조물 만들어 낸다. 아마도 견습 시기 일 때 몇 번의 시행착오를 통해서, 다른 일벌들의 경험을 물려받거나 조상들의 경험이 임베딩된 유전자가 이끄는 본능에 따라서 벌집을 만들고 있을 것이다.

패턴(pattern)은 프랑스의 고어 파톤(patron)에서 유래되었으며 파톤은 아버지로부터(from father)라는 의미다. 아버지의 생김새와 걸음걸이를 닮은 나의 모습을 떠올릴 수 있다면 패턴의 정확한 의미를 그려낸 것이다. 벌들 자신도 모르는 사이 육각형의 패턴으로 집을 짓고 있는 것이다.

소프트웨어를 만들 때도 앞선 시행착오와 경험을 참고해 구현 하는 것 보다 좋은 방법은 없다. 패턴을 활용한 소프트웨어 설계를 패턴지향 소프트웨어 아키텍처라고 한다. 패턴은 소프트웨어 전체적인 구조를 잡을 때나 지엽적인 설계 시, 언어에 종속적인 세부사항을 구현할 때도 참고할 수 있으며, 이를 구조 패턴(Architectural Pattern), 디자인 패턴(Design Pattern) 그리고 이디엄(Idioms) 혹은 마이크로 패턴(Micro Pattern)으로 구분 한다. 벌들이 만들어 내는 두께, 지름과 각도는 디자인 패턴이고, 육각형의 전체 모습은 구조 패턴인 것이다.

 

[그림 1] 1994년 출간되었지만 아직도 개발자들이 한번씩은 거처 간다는 GoF 디자인 패턴

 

싱글톤 패턴(Singleton Pattern)

윈도우 운영체제에서 작업관리자는 하나이다. 메모장을 여러 개 실행하고 있어도 입력을 대기하고 있는 캐럿이 활성화된 윈도우는 하나고, 마우스 포인터 역시 하나다. 로그를 담당하는 인스턴스와 같이 전체 애플리케이션에서 사용되는 기능을 하나의 인스턴스를 통해서 구현하고 싶거나, 데이터베이스 저장소에 접근하는 것처럼 초기화 및 운영과정에서 자원을 많이 써야 하는 경우 싱글톤 패턴을 사용한다. 인스턴스화 되는 객체를 한 개로 제한하여 추가 인스턴스 생성을 막고, 애플리케이션의 생성과 종료 시까지 해당 클래스의 기능을 사용하지 않는다면 애초에 인스턴스를 생성하지 않는 게으른 인스턴스화(Lazy instantiation)와 멀티스레드 환경을 고려해 보고, 이를 최신 트렌드에 맞게 싱글톤 패턴을 구현해 보도록 한다.

먼저, 전역적으로 접근하는 하나의 인스턴스를 만드는 방법에 대해서 생각해 보자. 단순하게 생각해 볼 수 있는 전역변수는 싱글톤이 아니다. 또한 전역 정적 변수 역시 싱글톤이 아닌데, 생성자 호출을 개발자가 통제할 수 없기 때문이다. C++ 표준에는 전역 객체에 대해서 생성자를 호출하는 시점에 대한 명확한 순서가 정의되어있지 않다. 예를 들어 정적 객체의 생성자에서 또 다른 정적 객체를 사용하는 경우 두 객체 사이의 생성자 호출 시점을 예측할 수 없다.

class MyClass {

public:

  static void myMethod();

};

void MyClass::myMethod() {

}

namespace MyNamespace {

  static void myMethod();

}

namespace MyNamespace {

  void myMethod() {

  }

}

 [리스트 1] C++에서의 정적 클래스 구현의 예. C++에서는 정적 클래스를 자바나 C#처럼 언어적 차원에서 지원해 주지는 않지만, 모든 맴버와 메소드를 정적으로 선언하여 유사하게 구현할 수 있다.

 

정적 클래스(static class)는 인스턴스간 관계가 기반인 객체지향적 애플리케이션 구조와는 맞지 않고 정적 맴버의 생명주기가 파일 단위(File scope)이기 때문에 적합하지 않다. static 키워드를 전체 맴버와 전체 메소드에 적용하지 않고 일부만 적용하여 클래스를 구성하는 경우 역시 객체지향 설계에 문제가 생기는 것은 여전하다. 다만 이를 응용하여 조금 다른 상황에서 활용 가능한 모노스테이트 패턴(Monostate pattern)으로 적용할 수 있다.

정적 클래스가 정적 맴버로만 구성되어 있기 때문에 클래스명은 단순히 네임스페이스를 제공해 주는 역할을 한다. 하지만 네임스페이스와 클래스의 쓰임이 같지는 않다. 템플릿 인자로 넘길 때는 반드시 타입 명이어야 하는데, 네임스페이스는 타입이 될 수 없으므로, 반드시 클래스로 선언해야 한다.

namespace A {

  static void f1() {};

  static void f2() {};

}

struct B {

  static void f1() {};

  static void f2() {};

};

template <typename T> struct C {

  void foo() {

    T::f1();

    T::f2();

  }

};

int main() {

  C<A> a; // C2882 에러

  C<B> b;

  b.foo();

  return 0;

}

 [리스트 2] 네임스페이스는 타입 이름으로 사용할 수 없다

 

디자인 패턴에 대해서 간략히 알 고 있는 개발자라면, 디자인 패턴명으로 검색해서 구현에 바로 적용하는데, 검색하다보면 C++로 된 소스코드가 아닌 자바나 C# 등으로 구현된 것을 자주 볼 수 있다. 타 언어의 정적 메소드를 이용한 패턴과 달리 C++ 언어는 클래스의 private 접근 한정자(Access Modifier) 심볼에 접근할 수 있고 private 정적 메소드를 통하여 인터페이스는 노출하지만 구현 사항이나 접근을 막을 수 있는 캡슐화가 가능하고, 정적 메소드에 대한 전방선언이나 오버로드가 불가능하다. 따라서 타 언어로 구현되어 있는 싱글톤 형태의 구현사항은 C++에서 완전히 같지 않으며 일부 사항에 대해서는 C++에 적용할 수 없음에 주의한다.

class Singleton {

private:

  static Singleton * instance_;

protected:

  Singleton() {};

public:

  static Singleton * Instance() {

    if (!instance_) {

      instance_ = new Singleton;

    }

    return instance_;

  }

};

Singleton * Singleton::instance_ = 0;

 [리스트 3] 기본적인 싱글톤 구현의 예

 

싱글톤의 구현은 생성자를 protected 접근 한정자에 적용하여 외부에서의 객체 생성을 방지하고, 정적 맴버인 Singleton* 타입의 인스턴스인 instance_를 public 접근 한정자인 Instance 메소드를 통해서만 접근하는데, 첫 번째 호출 될 때 instance_에 싱글톤 인스턴스를 생성하여 게으른 인스턴스화를 구현한다.

정적 메소드 Instance()에서의 구현사항을 보면 instance_를 체크해서 인스턴스를 생성한다. if문은 조건을 판단할 때 제수와 피젯수를 빼고 결과에 따라 점프하는 어셈블리 인스트럭션으로 이루어져 있다. 멀티스레드 환경일 경우 컨텍스트 스위칭 단위가 어셈블리 인스트럭션이므로 if문은 멀티스레드 환경에서 스레드 세이프(thread-safe) 하지 않다. 따라서 메소드가 동시에 두 개의 스레드에서 수행 될 때 new 문이 두 번 호출될 수 있으며, 이는 delete가 호출되지 않는 new가 다수 발생할 수 있음을 의미한다. 만일 instance_를 new 하는데 할당하는 자원이 크거나 하나의 자원이라면 문제가 될 수도 있는 상황이다.

 

#include "boost/noncopyable.hpp"

class Singleton : private boost::noncopyable {

private:

protected:

  Singleton() = default;

public:

  static Singleton& Singleton::Instance() {

    static Singleton instance;

    return instance;

  }

};

[리스트 4] 정적 인스턴스를 생성해 참조로 반환하는 싱글톤 구현의 예

 

멀티 스레드에서 안전한 가장 간단한 방법은 정적 메소드 Instance에서 정적 지역 인스턴스를 선언해 참조형태로 반환하는 것이다. 이러한 형태의 메소드로 구현한 방식을 마이어스 싱글톤(Meyers Singleton)이라고 하는데, 전역 인스턴스는 프로그램 시작과 함께 생성자가 호출되는 반면 정적 지역객체는 함수가 호출 되는 시점에 생성자가 호출되고 소멸자도 자동으로 호출되므로 생명주기 관리에 신경 쓸 필요가 없다. 다만 이를 구현하기 위해서는 비주얼 스튜디오 2015이상의 컴파일러가 필요하다.

부스트 라이브러리의 noncopyable 템플릿 클래스를 상속하면 복사 생성자와 할당 연산자를 생성하지 않아 객체의 복제를 막을 수 있어, 개발시 번거로운 부분을 줄여줄 수 있다.

최근 업데이트된 C++ 항목들을 적용하면 좀 더 쉽고 간편하면서 의도가 명확한 싱글톤 패턴을 구현할 수 있다.

class Singleton {

private:

  static std::once_flag onceflag_;

  static std::unique_ptr<Singleton> instance_;

protected:

  Singleton() = default;

  Singleton(const Singleton&) = delete;

  Singleton& operator=(const Singleton&) = delete;

public:

  static Singleton * Instance() {

    std::call_once(onceflag_

      , []() {

      instance_.reset(new Singleton);

    });

    return instance_.get();

  }

};

std::once_flag Singleton::onceflag_;

std::unique_ptr<Singleton> Singleton::instance_(nullptr);

[리스트 5] 의도가 명확한 싱글톤 패턴 구현의 예

 

C++의 클래스는 아무것도 없는 빈 클래스라고 해도 컴파일러가 기본적으로 생성자(Constructor), 복사 생성자(Copy Constructor), 할당연산자(Assignment Operator), 소멸자(Destructor)를 만들어 준다. 이동 생성자(Move Constructor) 및 이동 할당 연산자(Move Assignment Operator)는 컴파일러에 따라 구현이 다를 수도 있는데, 비주얼 스튜디오는 C++11 표준에 따라 기본으로 만들어 주지 않는다. 이동 생성자나 이동 할당 연산자를 선언한 경우 복사 생성자와 할당 연산자를 생성하지 않는다. 다만 C++ 표준은 복사 생성자나 소멸자가 선언된 경우 할당 연산자를 만들어 주지 않고, 할당 연산자나 소멸자가 선언된 경우 복사 생성자를 만들어 주지 않지만 비주얼 스튜디오의 경우 두 가지 모두 암시적으로 할당 연산자와 복사 생성자를 만들어 준다.

컴파일러가 생성해 주는 기본 메소드들을 원치 않는다면 해당하는 기본 메소드를 구현 없이 선언해 주는 방법으로 컴파일러에게 알려주었었다.

 

class noncopyable {

  //...

protected:

  noncopyable(const noncopyable&);

  noncopyable& operator=(const noncopyable&);

public:

  noncopyable() {}

  //...

};

[리스트 6] 클래스 외부 구현사항이 없다면 3개의 함수는 컴파일러가 기본적으로 생성하지 않는다

 

그러나 이러한 방식은 C++11 이후 비효율적인 방식이 되었다. 컴파일러 입장에서는 생성자가 비록 아무 일도 하지 않는다고 하더라도 무시 할 수 없도록 간주되어 기본 생성자를 자동으로 생성하는 것보다 비효율 적인 형태로 구현되게 된다. 구현체가 없다고 하더라도 프랜드 클래스에서는 선언문을 통해 호출할 수가 있고, 만일 호출할 경우 구현체가 없기 때문에 링크오류가 발생한다. 무엇보다도 클래스 메소드는 헤더파일 뿐 아니라 소스코드 파일에서도 정의할 수 있어 직관적이지 못하기 때문에 이러한 형태는 권장하지 않는다.

비주얼 스튜디오 2013부터 추가된 기본설정(Defaulted function) 및 삭제된 함수(Deleted function)는 default와 delete를 클래스 내부에 명시함으로써 컴파일러가 생성하고 생성하지 않는 기본 함수들을 보다 명확히 구분할 수 있게 되었다. 만일 삭제된 함수를 호출한다면 이는 런타임이 아닌 컴파일 타임 에러로 발생하기 때문에 개발자가 미리 대응할 수 있다.

컴파일러가 만들어 주는 클래스의 복사 생성자나 할당 연산자를 사용시 깊은 복사(Deep copy)를 수행하는 복사 시맨틱(Copy Semantics)이 일어난다. 하지만 벡터와 같은 컨테이너에 작업을 수행하면 원본 객체는 사용하지 않게 되어 낭비가 발생한다. 소유권 이전은 얕은 복사(Shallow copy)이후 원본을 더미 객체로 만들어 소유권을 이전시키는 이동 시맨틱(Move Semantics)이 일어나는데, 대표적으로 이동 생성자나 unique_ptr이 있다. unique_ptr에는 복사 생성자나 할당 연산자가 없기 때문에 std::move()를 사용하거나 reset() 메소드를 이용해 객체를 할당하며 스마트 포인터기 때문에 자원해제는 안전하게 구현되어 있다.

 

void foo(int x, int y, int ResourceID); // 1

void foo(int x, int y, const char *name); // 2

int main() {

  foo(1, 2, NULL); // 1번 호출

  foo(1, 2, nullptr); // 2번 호출

  return 0;

}

[리스트 7] nullptr을 사용하면 템플릿에서 오버라이딩 된 함수의 인자나 템플릿의 타입 추론이 개발자의 의도대로 동작하며 실수할 가능성을 줄여준다

 

std::once_flag는 구현사항을 숨기는 불투명 포인터(Opaque Pointer)를 이용한 Pimpl(Pointer IMPLementation) 이디엄을 사용했다. std::once_flag는 초기화 하지 않으며 std::call_once와 연동되어 멀티스레드 환경에서 단 한번만 실행되는 것을 운영체제 차원에서 보장해 주도록 구현되었다.

 

struct once_flag {

constexpr once_flag() _NOEXCEPT : _Opaque(0) {}

once_flag(const once_flag&) = delete;

once_flag& operator=(const once_flag&) = delete;

void *_Opaque;

};

[리스트 8] 불투명 포인터(Opaque Pointer)를 이용한 once_flag 구현의 예

 

지금은 한계가 있어서 잘 사용되지는 않는 Double-Checked Locking Pattern을 이용하는 방법, std::memory_order를 이용한 방법 등의 내용의 이야기가 더 있으나, 지면관계상 생략한다.

 

데코레이터 패턴(Decorator Pattern)

상속을 이용하면 기능을 확장하기가 매우 쉽기 때문에 개발자들은 언제나 상속 구현에 대한 유혹을 느낀다. 하지만 상속을 이용한 기능 확장을 애용할 경우 애플리케이션이 확장되면서 계속 늘어가는 구현 클래스(Concrete Class)를 관리해주어야 하고, 많은 클래스에 중복되는 기능을 구현해야 되는 부담과 비즈니스 로직이 각 클래스 메소드에 구현되어 있어 유지보수를 진행할수록 스파게티 코드가 되어 버리는 문제가 있다.

 

class Animal {

public:

  virtual void show() const = 0;

};

class Tiger : public Animal {

public:

  virtual void show() const {

    std::cout << "Tiger" << std::endl;

  }

};

class ScaryTiger : public Tiger {

public:

  void show() const {

    std::cout << "Scary ";

    Tiger::show();

  }

};

int main() {

  Animal * tiger = new ScaryTiger();

  tiger->show();

}

결과 :

Scary Tiger

[리스트 9] 시스템이 커질수록 ScaryTiger와 같은 클래스는 늘어나고 ScaryTiger::show 메소드에 비즈니스 로직이 정적 구현되어 있어 점차 유지보수가 어려워진다.

 

이런 상황에서 데코레이터 패턴을 적용하면 비즈니스 로직을 구현하기 위한 클래스의 책임을 동적으로 추가할 수 있다. 지속적으로 서브 클래스를 생성하거나 서브 클래스의 구현에 비즈니스 로직이 구현되는 것 보다 융통성 있는 방법을 제공해 줄 수 있다.

데코레이터 패턴을 구현해 보기 위해 인터페이스인 Animal 클래스를 정의하고 이를 상속한 Dog, Cat 클래스를 정의했다.

 

class Animal {

public:

  virtual void show() const = 0;

};

class Dog : public Animal, boost::noncopyable {

public:

  void show() const override final {

    std::cout << "Dog" << std::endl;

  }

};

class Cat : public Animal, boost::noncopyable {

public:

  void show() const override final {

    std::cout << "Cat" << std::endl;

  }

};

[리스트 10] Animal 인터페이스 클래스를 상속한 Dog, Cat 클래스 구현의 예

 

순수 가상함수 show를 상속한 Dog과 Cat 클래스의 show 메소드에는 override와 final 키워드를 사용했다. override키워드를 사용하면 메소드를 명시적으로 오버라이딩 함을 컴파일러에게 알려주어 컴파일 시간에 개발자의 실수를 찾아낼 수 있다.

 

class Base {

virtual void func(int nVal) {};

};

class Derived : public Base {

virtual void func(float nVal) {}; // int의 오타

};

[리스트 11] 문법적으로 문제없는 코드

 

만일 오버라이딩 된 메소드의 인자 타입을 실수로 잘못 코딩 했더라도 문법적으로 문제없기 때문에 컴파일러는 에러 없이 빌드한다. 이때 명시적으로 오버라이딩 한다는 override 키워드를 사용하면 빌드시 컴파일러가 오버라이딩 된 메소드를 점검하여 문제가 있을 경우 컴파일 에러를 통해 개발자에게 알려준다. final 키워드는 현재 구현 클래스 이후로 더 이상 상속을 하지 않겠다는 명시적 선언이며 컴파일러는 빌드 과정에서 이를 확인한다.

 

class Feature : public Animal {

private:

  Animal const& animal_;

public:

  Feature() = default;

  Feature(Animal& animal) : animal_{ animal } {}

  void show() const override {

    animal_.show();

  }

};

[리스트 12] 기존 기능에 새로운 책임을 추가하기 위한 데코레이터 클래스 Feature 정의의 예

 

데코레이터 클래스는 생성자를 통하여 기존 기능이 구현된 인스턴스를 받아 맴버로 관리한다. 데코레이터 클래스를 상속받은 구현 클래스에서 추가 기능을 구현하며 데코레이터 클래스의 인터페이스인 show를 호출하여 주 기능을 수행한다.

 

class Scary : public Feature {

public:

  Scary(Animal& animal) : Feature(animal) {}

  void show() const override final {

    std::cout << "Scary ";

    Feature::show();

  }

};

class Big : public Feature {

public:

  Big(Animal& animal) : Feature(animal) {}

  void show() const override final {

    std::cout << "Big ";

    Feature::show();

  }

};

[리스트 13] 추가되는 기능을 구현하고 데코레이터 클래스가 구현한 공통 인터페이스를 통하여 주 기능을 수행한다

 

데코레이터 클래스를 상속받고 추가 기능이 구현된 Scary와 Big 클래스를 구현한다. 구현 클래스의 생성자에서 주 기능, 혹은 추가 기능 인스턴스를 받아 데코레이터 생성자에 전달하는데, 데코레이터 클래스를 이용하여 새로운 책임을 계속 동적으로 추가 할 수 있다.

 

int main() {

  Dog dog{};

  dog.show();

 

  Big bigDog{ dog };

  bigDog.show();

 

  Scary scaryBigDog{ bigDog };

  scaryBigDog.show();

 

  Scary(Big(Cat())).show();

  return 0;

}

결과:

Dog

Big Dog

Scary Big Dog

Scary Big Cat

[리스트 14] 추가 기능 클래스의 인스턴스에 또 다른 추가 기능 혹은 주 기능의 인스턴스를 전달하여 추가 기능 클래스의 메소드 구현에 따라 차례로 수행되게 된다.

 

주 기능 클래스를 선언하여 메소드를 호출하면 주 기능이 수행된다. Dog 클래스의 인스턴스를 생성해 show 메소드를 호출하거나 Big 클래스의 인스턴스에 Dog 클래스의 인스턴스를 전달해 데코레이터 클래스를 통해 유연하게 기능을 추가할 수도 있다. Scary 클래스에 Big 인스턴스를 전달해 다른 기능을 추가할 수도 있다.

데코레이터 패턴의 핵심은 주 기능과 추가 기능을 동적으로 추가하고 구현사항이 서로 독립적으로 이루어 지는데 있다. 목적만 달성할 수 있다면 구지 클래스를 정의하지 않아도 된다. 템플릿 클래스나 람다식을 이용해 데코레이터 패턴 특유의 동적인 기능 추가와 독립된 구현을 유사하게 구현할 수 있다.

 

template <typename Func>

class TagUsingTamplate {

private:

  Func func;

public:

  TagUsingTamplate(const Func& func) : func{ func } {}

  void operator()() const {

    std::cout << "<HTML>";

    func();

    std::cout << "</HTML>" << std::endl;

  }

};

template <typename Func>

auto make_TagUsingTamplate(Func func) {

  return TagUsingTamplate<Func>{func};

}

std::string data = R"(

<BODY>

<P>contents</P>

</BODY>)";

int main() {

  auto tagUsingTemplate = make_TagUsingTamplate([=]() {std::cout << data << std::endl; });

  tagUsingTemplate();

}

 

[리스트 15] 템플릿을 이용한 데코레이터 패턴 역할의 구현

 

C++에서 다루는 문자열의 특징은 이스케이프 시퀀스(Escape Sequences)가 있어서 줄 바꿈, 가로 탭, 캐리지 리턴 등을 ‘\n’, ‘\t’, ‘\r’와 같이 문자열에 포함할 수 있다는 것이다. 다만, 이 때문에 생긴 불편함도 있는데 역 슬래시나 작은 따옴표, 큰 따옴표등 일반적으로 텍스트에서 사용되는 문자들도 이스케이프 시퀀스가 할당되어 있어 ‘\\’, ‘\’’, ‘\“’ 와 같이 명시해야 하기 때문에, 인터넷이나 텍스트파일에서 복사한 문자열을 그대로 사용할 수 없다는 것이다.

// text.txt

<BODY LINK = "#0000ff" BGCOLOR = "#ffffff">

C:\WORK\TEST.H

  < / BODY>

 

  // text.cpp

  std::string cppString = "<BODY LINK=\"#0000ff\" BGCOLOR=\"#ffffff\">\n"

  "C:\\WORK\\TEST.H\n"

  "</BODY>";

std::string cppRSL1 = R"(

<BODY LINK = "#0000ff" BGCOLOR = "#ffffff">

C:\WORK\TEST.H

</BODY>)";

std::string cppRSL2 = R"*(

"(string)"

)*";

[리스트 16] 텍스트 파일에 포함된 일부 문자를 이스케이프 시퀀스로 바꾸어 주어야 C++ 문자열로 사용할 수 있다

 

C++11 에서는 원시 문자열을 그대로 소스코드에서 사용할 수 있는 원시 문자열 리터럴(Raw String Literals)을 제공해 주며 비주얼 스튜디오 2013 버전부터 사용할 수 있다. 원시 문자열 리터럴은 R“( 으로 시작하여 ”) 으로 닫아 주어 C++ 문자열과 구분할 수 있다. 만일 원시 문자열 리터럴의 예약 문자가 문자열에 포함되어 있을 경우 R“*( 으로 가운데 별표(Asterisk)를 추가해 준다.

TagUsingTamplate 템플릿 클래스는 템플릿 인자로 함수를 받는데 생성자의 인자를 통해서 주 기능을 하는 함수를 얻고 해당 함수를 func 맴버에 저장한다. 그리고 함수 객체를 통해 추가 기능과 주 기능을 실행한다.

main함수에서 주 기능은 람다로 정의했다. 따라서 TagUsingTamplate 템플릿 클래스의 템플릿 인자로 타입을 넘겨주기가 곤란한데, 템플릿 함수 타입 추론을 해주는 make_TagUsingTamplate 핼퍼 함수를 통하여 TagUsingTamplate 템플릿 클래스를 중계해 주었다.

 

class Tag {

private:

  std::function<void()> func;

public:

  Tag(const std::function<void()>& func)

    : func{ func } {}

  void operator()() const {

    std::cout << "<HTML>";

    func();

    std::cout << "</HTML>" << std::endl;

  }

};

int main() {

  Tag tag{ [=]() {std::cout << data << std::endl; } };

  tag();

}

결과:

<HTML>

<BODY>

<P>contents< / P>

< / BODY>

< / HTML>

[리스트 17] std::function을 이용한 구현

 

C++11에 추가된 std::function을 이용하면 템플릿 클래스를 바로 사용할 수 있는데, std::function이 중계역할을 해주기 때문이다. Tag 클래스는 생성자로 std::function 객체를 받는데, 이 객체는 main 함수에서 람다식으로 넘겨주었다. 앞서 템플릿 구현과 마찬가지로 Tag의 함수 객체를 이용해 주 기능과 추가 기능을 실행한다.

 

template <typename> class TagUsingVariadicTemplate;

template <typename Head, typename ... Tail>

class TagUsingVariadicTemplate<Head(Tail ...)> {

private:

  std::function<Head(Tail ...)> func;

public:

  TagUsingVariadicTemplate(const std::function<Head(Tail...)>& func) : func{ func } {}

  void operator() (Tail ... args) {

    std::cout << "=== start ===" << std::endl;

    func(args...);

    std::cout << "==== end ====" << std::endl;

  }

};

template<typename Head, typename... Tail>

auto make_TagUsingVariadicTemplate(Head(*func)(Tail...)) {

  return TagUsingVariadicTemplate<Head(Tail...)> {

    std::function<Head(Tail ...)>(func)

  };

}

double foo(int a, char b, float c) {

  std::cout << a << ", " << b << ", " << c << std::endl;

  return 1;

}

int main() {

  auto boo = make_TagUsingVariadicTemplate(foo);

  boo(1, '2', 3.0);

  return 0;

}

결과:

== = start == =

1, 2, 3

==== end ====

[리스트 18] 가변 템플릿을 이용한 함수 객체 인자 처리의 예

 

C++언어는 가변인자와 관련된 표현은 모두 ... (생략연산자, ellipsis operator)을 사용한다. 가변 템플릿역시 템플릿 인자 추론을 위해 make_TagUsingVariadicTemplate 핼퍼 함수를 이용한다. 핼퍼 함수는 추론된 가변 타입과 함수 객체가 std::function로 TagUsingVariadicTemplate의 생성자 인자로 전달되며 이후 과정은 템플릿 버전의 과정과 유사하다.

 

reference

[1] Rainer Grimm: Thread-Safe Initialization of a Singleton

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

[2] Decorator Design Pattern in C++

https://sourcemaking.com/design_patterns/decorator/cpp/2

[3] MSDN: Explicitly Defaulted and Deleted Functions

https://msdn.microsoft.com/en-us/library/dn457344(v=vs.140).aspx

 

 

[코멘트] 좋음
2018-04-18 15:02
 flowtide  flowtide님께 메시지 보내기flowtide님을 내 주소록에 추가합니다.flowtide님의 개인게시판 가기 
잘 읽었습니다. 데코레이션 패턴 정말 유용하겠네요.
저장 취소
코멘트쓰기
  좋음   놀람   궁금   화남   슬픔   최고   침묵   시무룩   부끄럼   난감
* 코멘트는 500자 이내(띄어쓰기 포함)로 적어주세요.
목록 보기   지금 보고 계시는 글을 회원님의 my Mblog >> 스크랩에 넣어두고 다음에 바로 보실 수 있습니다.  
회사소개  |   개인정보취급방침  |  제휴문의  |   광고문의  |   E-Mail 무단수집거부  |   고객지원  |   이용안내  |   세금계산서
사업자등록번호 안내: 220-81-90008 / 통신판매업신고번호 제 2017-서울구로-0055호 / 대표: 홍영준, 서민호
08390, 서울시 구로구 디지털로32길 30, 1211호 / TEL. 02_6719_6200 / FAX. 02-6499-1910
Copyright ⓒ (주) 데브피아. All rights reserved.