S T U D Y/C++ - 해당되는 글 3건

Effective C++ 263page에 나오는 내용



* review

우선 NVI 를 통해 템플릿메소드패턴을 구현하는 방법에 대해 알아보았어요!


  214 class 부모

  215 {

  216 private:

  217     virtual Dofunction()

  218     {

  219     // 자식이 상속받아서 재정의할수있음

  220     // private에 있으므로 절대 호출은할수없지만 재정의!만 가능

  221     }

  222 public:

  223     func()

  224     {

  225         // 사전작업. 공통된 루틴

  226         Dofunction();

  227         // 사후작업. 공통된 루틴

  228     }

  229 };

  230 

  231 class 자식 : class 부모

  232 {

  233     virtual Dofunction()

  234     {

  235         //자식마다 그때그때 다른루틴을 이곳에

  236     }

  237 };





이걸 응용해서
함수포인터로 구현한 전략패턴

  239 typedef int (*FuncPointer)(double);

  240 //double을 매개변수로 가지며

  241 //int를 리턴하는 함수를 위한 포인터를

  242 //Func라고 정의해주는 define문

  243 

  244 Function( FuncPointer fp )

  245 {

  246     // 사전작업

  247     fp();

  248     // 사후작업

  249 }



이렇게 해줬을때의 문제점?

-> 저 펑션은 지금 전역함수가아닌가?
그렇다면 클래스 내부의 private에 감춰진 멤버변수들에 접근이 불가능해지므로
몹시 불편해진다.

고로 우리가 찾은 해결책.

std::tr1::function<int (double)> FuncPointer;

저 위에 펑션포인터보다 훨씬 더 똑똑하다.
tr1의 function계열의 객체는 전역함수포인터, 멤버함수포인터, 함수객체까지 담을수 있어요!

그러나 이렇게했을때의 문제점은?

-> 우리가 만약 멤버함수포인터를 넘긴다면
멤버함수 포인터는 저 위에  써준 함수의 시그너쳐에 맞게

더블타입만을 매개변수로 가질것같은 느낌이들지만
사실은 그렇지않죠.

클래스의 멤버변수이기때문에 기본적으로 멤버함수에는
우리눈에 보이지않는 인자가 숨어있어요
자기가 어떤 객체에서 호출될 함수인지 알아야하기때문에
객체이름이 숨어있다고 볼수있죠

그래서 사실상
int를 리턴하며 void를 받아오는 함수가 아니라서
FuncPointer로는 취급할수 없어져버린다는 결론

고로 여기서 또다른 솔루션

  244 Function( FuncPointer fp )

  245 {

  246     // 사전작업

  247     fp();

  248     // 사후작업

  249 }

이 함수를 호출할때

Function(  std::tr1::bind(클래스이름::멤버함수이름, 객체이름)  );

이렇게 써주시면 되어요
여기서 bind의 역할은

위에 노란형광펜 부분에 있는 '객체이름'에 쓰여진
이름의 객체가 인자로 넘어가는거죠!

Function의 매개변수로 넘어가야 할 값은
보통의 경우

Function(a); // double을 매개변수로 가지고 int를 리턴하는 함수포인터 a를 인자로 넘길께.

그렇지만

Function(  std::tr1::bind(AAA::func, Name)  );

이 경우엔?
AAA클래스에서 double을 매개변수로 가지고 int를 리턴하는데
Name객체에서 실행되는 함수포인터를 인자로 넘길께!!!!!!!!!!!!!!!!!!!!!!

ㅇㅋㅇㅋ?

우리가 아리달쏭했던 부분은 단지
바인드가 뭐하는앤가? 였는데 너무 장황하게 설명한건
그냥 복습차 ㅋㅋ

그럼 안녕히들주무세여-

      S T U D Y/C++  |  2008. 7. 16. 00:13




0.    시작하기 전에 알아두면 좋은 이야기 ? 값과 타입이야기

 

1, 2, 3 또는 3.4 같은 것들은 이라고 부릅니다. 또한, int, double, char 같은 것들은 값이 아닌 타입이라고 부르지요.  값 자체는 함수 오버로딩에 사용될 수 없지만 타입은 함수 오버로딩에 사용될 수 있습니다. ,

void foo( int n) {}

 

void goo( int n) {}

void goo( double n) {}

 

int main()

{

        foo(1);

        foo(2); // 1,2는모두int라는동일타입입니다. 따라서같은함수를호출합니다.

 

        int n = 10;

        double d = 2.3;

 

        goo(n);

        goo(d); // nd는타입이다르므로각각다른함수가호출됩니다.

}

 

당연한 이야기 같지만, 이 사실이 뒤에서 중요하게 등장하게 되니까, 잘 기억하시길

 

1.    Max function template

 

먼저 아래의 template Max()함수를 보도록 하지요.

#include <iostream>

using namespace std;

 

template<typename T>

const T& Max( const T& a, const T& b )

{

        return a < b ? b : a;

}

 

int main()

{

        int a = 1;

        int b = 2;

 

        int x = Max(a, b);  // (A)

        cout << x << endl;

 

        int* p = Max(&a, &b); // (B)

        cout << *p << endl;

}

                                                                                

A의 경우는 당연히 T int로 되므로 아무 문제가 없습니다. 하지만 B의 경우는 T int*로 되므로 결국 값이 아닌 포인터 끼리의 비교를 하는 코드가 됩니다.

 

이제 좀더 똑똑한 Max()를 구현해 보도록 하겠습니다. , 값이 전달되면 그냥 < 연산을 수행하고 포인터가 전달되면 포인터가 가르키는 값을 참조해서 < 연산을 수행하도록 Max를 변경해 보도록 하지요.. 아래와 같은 모양이 될 것입니다.

 

template<typename T>

const T& Max( const T& a, const T& b )

{

        if ( T is Pointer )

               return *a < *b ? b:a;

 

        return a < b ? b : a;

}

 

이제 문제는 T가 포인터인지 아닌지를 알아 내어야 합니다.

 

2.    is_pointer<>의 구현

 

임의의 타입 T가 포인터인지 알아 내려면 template 부분 전문화(partial specialization, 부분특화) 문법을 사용하면 쉽게 구현할 수 있습니다.

아래 처럼 구현 하면 됩니다.

 

template<typename T> struct is_pointer

{

        enum { value = false };

};

 

template<typename T> struct is_pointer<T*>

{

        enum { value = true };

};

 

template<typename T> struct is_pointer<const T*>

{

        enum { value = true };

};

 

// is_pointertest하기위한함수.

template<typename T> void test( const T& a)

{

        if ( is_pointer<T>::value )

               cout << "T is pointer" << endl;

        else

               cout << "T is not pointer" << endl;

}

 

int main()

{

        int x = 0;

 

        test(x);

        test(&x);

 

        int y[10];

        test(y);       // y는배열입니다.

}

 

먼저 template 기본 버전(primary template)을 사용해서 is_pointer의 기본 모양을 만듭니다. 이때 내부적으로는 value 라는 이름의 enum 상수를 넣는데 기본적으로는 false 로 초기화 합니다.

 

이제 T* const T* 에 대해서 template 부분 특화 버전을 제공하는데 value true로 초기화 합니다.

따라서

           is_pointer<T>::value

 

에서 T가 포인터가 아니면 is_pointer의 기본 버전을 사용하므로 false T가 포인터 라면 부분 특화 버전을 사용하므로 true가 나오게 됩니다.

 

이제 우리는 함수 template안에서 T가 포인터 인지 아닌지를 구별할 수 있게 되었습니다.

 

여기서 또 하나 중요한 사실은 is_pointer<T>::value true인지 false인지는 실행시간이 아닌 컴파일시간에 결정 됩니다. , 아래의 문장은

          

      if ( is_pointer<T>::value )

               cout << "T is pointer" << endl;

        else

        cout << "T is not pointer" << endl;

 

T가 포인터가 아니라면 컴파일 후에는

 

      if ( false )

               cout << "T is pointer" << endl;

        else

        cout << "T is not pointer" << endl;

 

 

가 됩니다. if() 문 안의 구문이 변수가 아닌 상수이기 때문에 컴파일러 최적화에 의해서 다시 아래 처럼 최적화된 코드가 생성됩니다.

 

cout << "T is not pointer" << endl;

 

결론은 is_pointer<>를 사용해도 실행시 성능상의 오버헤드가 없다는 이야기 입니다.

, 컴파일 시간이 조금 오래 걸리긴 합니다.

일 때 is_pointer<>는 일반 함수와 닮은 점이 있습니다.

 

int a = square(5);

bool b = is_pointer<T>::value;

 

일반 함수 square()실행시간에 5라는 값을 인자로 전달해서 함수 결과를 리턴해 줍니다.

하지만 is_pointer<T>::value 는 컴파일 시간에 T라는 타입을 인자로 전달해서 bool(또는 다른 타입인 경우도 있습니다.)을 리턴해 주고 있습니다.

이처럼 컴파일시간에 타입을 인자로 가지는 함수를 보통 메타 함수라고 부릅니다.

 

3.    Is_pointer<> Max templte의 결합

 

, 이제 is_pointer<>가 완성되었으므로 Max() 를 다시 구현해 보도록 하겠습니다.

 

 

template<typename T>

const T& Max( const T& a, const T& b )

{

        if ( is_pointer<T>::value )

               return *a < *b ? b : a;

 

        return a < b ? b : a;

}

 

int main()

{

        int a = 1, b = 2;

 

        Max(&a, &b); // (A) 포인터를전달할경우

        // Max(a, b);   // (B) 값을전달할경우

}

 

아무 문제가 없을 꺼 같지만 아직 문제가 남아 있습니다. (B)의 주석을 제거 해보시면 에러가 나오는 것을 보실 수 있습니다. 왜 그럴까요 ? 컴파일러가 어떤 과정을 거쳐서 template을 인스턴스화(T를 해당 타입으로 치환하는 과정)하는지를 생각해 봅시다

 

(1)   먼저 template 자체의 문법이 맞는지 확인합니다.

(2)   T를 해당 타입으로 변경한 코드를 생성합니다.

(3)   다시 특정 타입으로 인스턴스화된 함수(클래스)의 문법이 맞는지 확인합니다.

(4)   코드 최적화 과정을 수행합니다.

 

이제 (A)의 경우와 (B)의 경우 컴파일러가 어떻게 Max()를 생성하는지를 생각해 봅시다.

먼저, A의 경우는 T int*로 변경됩니다. , 아래 처럼 Max가 생성됩니다.

const int*& Max( const int*& a, const int*& b )

{

        if ( true )

               return *a < *b ? b : a;

 

        return a < b ? b : a;

}

위 코드는 문법적으로는 아무 문제가 없습니다. 또한 최적화를 수행해서 마지막 문장은 제거해 버립니다.(if가 항상 true 이므로)

 

이번에 B의 경우 입니다. T int로 변경 됩니다. 아래 코드를 보세요..

 

const int& Max( const int& a, const int& b )

{

        if ( false )

               return *a < *b ? b : a; // error. int& 타입에대해*를수행할수없습니다.

 

        return a < b ? b : a;

}

문제가 되는 부분이 나왔군요.. a int& 타입인데.. *a를 수행하는 코드가 생성 되었군요최적화가 먼저 이루어져서 if 문을 컴파일 하지 않았다면 문제가 없을텐데, 현재 대부분의 C++ 컴파일러는 이 경우 에러를 발생 시키기 됩니다.

 

해결책이 없을까요 ? 자 이제, 해결을 위해서 2000년도에 C++ Report지에 게재 되었던 generic 프로그램의 1인자인 Andrei Alexandrescu( Modern C++의 저자)의 글을 소개해야 되겠군요.

 

4.    정수를 타입으로 int2type<>

 

Modern C++의 저자인 안드레이 알렉산드레스쿠는 2000년도에 C++Report지에 정수 값을 타입으로 변경해주는 int2type<> 이라는 template을 처음으로 소개 합니다. 그 구현은 아래와 같이 아주 단순하게 되어 있습니다.

template<int N> struct int2type

{

        enum { value = N };

};

 

template의 목적은 1, 2 등의 상수값 가지고  새로운 타입을 만들수 있게 됩니다. 장점은 상수값을 가지고 함수 오버로딩으로 사용할 수 있게 됩니다.

 

아래 코드를 보고 잘 생각해 보세요

 

void foo( int2type<0> a)

{

        cout << "int2type<0>" << endl;

}

 

void foo( int2type<1> a)

{

        cout << "int2type<1>" << endl;

}

 

int main()

{

        int2type<0> t0;

        int2type<1> t1;

 

        if ( typeid(t0) == typeid(t1))

               cout << "t0, t1 은동일타입입니다." << endl;

        else

               cout << "t0, t1 은다른타입입니다." << endl;

 

        // 이제정수상수0,1을가지고서로다른함수를호출할수있게되었습니다.

        foo(t0);

        foo(t1);

}

 

5.    Is_pointer<>, int2type<>, Max 의 결합

 

이제 3가지를 모두 결합해서 문제를 해결해 보도록 하겠습니다.

완성된 코드는 아래와 같습니다.

#include <iostream>

using namespace std;

 

template<typename T> struct is_pointer

{

        enum { value = false };

};

 

template<typename T> struct is_pointer<T*>

{

        enum { value = true };

};

 

template<typename T> struct is_pointer<const T*>

{

        enum { value = true };

};

 

template<int N> struct int2type

{

        enum { value = N };

};

 

// T가포인터가아닌경우의Max 구현입니다. - 3번째인자를조심해서보세요

template<typename T> const T& Max_impl(const T& a, const T& b, int2type<0> )

{

        return a < b ? b : a;

}

 

// T가포인터인경우

template<typename T> const T& Max_impl(const T& a, const T& b, int2type<1> )

{

        return *a < *b ? b : a;

}

 

// 실제Max는내부적으로는Max_impl을사용하게됩니다.

template<typename T> const T& Max( const T& a, const T& b )

{

        // bool 값상수를가지고타입으로변경한후인자로전달하기위해서익명의객체를생성합니다.

        return Max_impl( a, b, int2type<is_pointer<T>::value>());

}

 

int main()

{

        int x = 10;

        int y = 20;

 

        int n = Max(x, y);

        int* p = Max(&x, &y);

 

        cout << n << endl;

        cout << *p << endl;

}

 

 

,~ 힘드시죠.. 조금만 참으면 됩니다.

여기까지는 TR1의 내용은 아니고 TR1이 나오기 전에 사용되는 기법들입니다.

이제 TR1의 경우를 보도록 하지요..

 

6.    TR1 ? integral_constant

 

먼저 TR1에서 type_traits를 사용하려면 <type_traits> 헤더가 필요 합니다. 또한, TR1의 모든 요소는 std namespace 안에 tr1 이라는 namespace 안에 있습니다.

 

TR1이 제공하는 type traits의 가장 기본은 integral_constant<>입니다. int2type을 개선 한 것으로 생각할 수 있는데, 아래 처럼 사용하시면 됩니다.

#include <iostream>

#include <type_traits>

using namespace std;

using namespace std::tr1;

 

int main()

{

        integral_constant<int, 0> t0;

        integral_constant<int, 1> t1;

        integral_constant<bool, true> t2;

        //integral_constant<double, 3.2> t3; // error. 정수계열상수만가능합니다.

 

        if ( typeid(t0) == typeid(t1))

               cout << "같은타입" << endl;

        else

               cout << "다른타입" << endl;

}

실제 구현이 어떻게 되어있는지 살펴 보도록 하지요.(구현은 컴파일러 환경마다 다를수 있습니다. 아래 코드는 VC2008 featured pack을 설치한 경우 입니다. Dinkumware 사에서 만든 구현입니다.)

 

template<class _Ty,    _Ty _Val>

struct integral_constant

{

        static const _Ty value = _Val;

        typedef _Ty value_type;

        typedef integral_constant<_Ty, _Val> type;

};      

typedef integral_constant<bool, true> true_type;

typedef integral_constant<bool, false> false_type;

 

 

int2type<> int 형의 상수만을 사용했던 것에 비해서 integral_constant<>는 모든 정수 계열을 타입으로 변경할 수 있게 되었습니다. 또한, enum 상수 대신에 static const 멤버로 값을 보관하고 있으면 value_type type 이라는 typedef 2개를 추가 하고 있습니다.

 

이제, 중요한 것은 아래의 2개의 typedef입니다.

TR1 type_traits에서 아주 많이 사용하는 개념이니까, 확실하게 알아 두셔야 합니다.

 

bool 형태의 값 true false를 가지고 true_type, false_type을 만들고 있습니다. true, false는 둘 다 bool 이라는 동일 타입이지만 그것으로 만든 true_type false_type은 분명히 다른 타입 입니다.

 

7.    is_array<> 의 구현

 

T가 배열인지 아닌지를 알아내는 메타 함수를 TR1은 아래와 같이 제공하고 있습니다.

 

template<class _Ty> struct is_array : false_type

{      

};

 

template<class _Ty, size_t _Nx> struct is_array<_Ty[_Nx]> : true_type

{

};

 

template<class _Ty> struct is_array<_Ty[]> : true_type

{

};

 

primary template 버전과 부분 특화 버전 모두 true_type 또는 false_type으로 상속 받고 있기 때문에( 구조체 상속에서 접근변경자를 생략하면 기본적으로 public 상속입니다.) 모두 부모로부터 value 라는 static const 멤버를 상속 받기 때문에 아래 처럼 사용할 수 있습니다

 

#include <iostream>

#include <type_traits>

using namespace std;

using namespace std::tr1;

 

template<typename T> void test( const T& x)

{

        if ( is_array<T>::value )

               cout << "배열" << endl;

        else

               cout << "배열이아닙니다." << endl;

}

int main()

{

        int x[10];

        int n;

 

        test(x);

        test(n);

}

 

하지만, 여기서 재미 있는 사실은 기본 버전은 false 타입에서 상속 받고 부분 전문화 버전은 true type에서 상속되었다는 사실 입니다. 따라서 아래 처럼 오버로딩된 함수를 호출하는 용도로 사용하기가 편리 하게 되어 있습니다.

 

#include <iostream>

#include <type_traits>

using namespace std;

using namespace std::tr1;

 

void foo( true_type )

{

        cout << "배열" << endl;

}

void foo( false_type )

{

        cout <<"배열이아닌경우" << endl;

}

 

template<typename T> void test( const T& x)

{

        // is_array의임시객체를생성합니다.

        // T가배열이라면임시객체는true_type의자식입니다.

        // T가배열이아니라면임시객체는false_type의자식입니다.

        foo( is_array<T>());

}

 

int main()

{

        int x[10];

        int n;

 

        test(x);

        test(n);

}

 강석민강사님 데브피아에 쓰신 글 펌-

속은 비어있는데 할짓은 다하는 저런 깔끔한 생각은 대체
으음-_ -신기하다

      S T U D Y/C++  |  2008. 7. 7. 18:42




Generic: Change the Way You Write Exception-Safe Code — Forever<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Andrei Alexandrescu and Petru Marginean

Let's face it: writing exception-safe code is hard. But it just got a lot easier with this amazing template. Alexandrescu and Marginean have come up with a way to automatically invoke an "undo" operation of your choosing, in the event that an exception is thrown. Using this template you can easily write functions that either succeed or "rollback" to a previous state-all without cumbersome try...catch blocks.


Call it overselling, but we'll tell you up front: we have killer material for this article. This is only because I convinced my good friend Petru Marginean to be my coauthor. Petru has developed a library facility that is helpful with exceptions. Together, we streamlined the implementation until we obtained a lean, mean library that can make writing exception-safe code much easier.

Let's face it, writing correct code in the presence of exceptions is a not an easy task. Exceptions establish a separate control flow that has little to do with the main control flow of the application. Figuring out the exception flow requires a different way of thinking, as well as new tools.

Writing Exception-Safe Code Is Hard: An Example

Let's say you are developing one of those trendy instant messaging servers. Users can log on and off the system and can send messages to each other. You hold a server-side database of users, plus in-memory information for users who are logged on. Each user can have friends. The list of friends is also kept both in the database and in memory.

When a user adds or removes a friend, you need to do two things: update the database and update the in-memory cache that you keep for that user. It's that simple.

Assuming that you model per-user information with a class called User and the user database with a UserDatabase class, the code for adding a friend might look like this:

class User

{

    ...

    string GetName();

    void AddFriend(User& newFriend);

private:

    typedef vector<User*> UserCont;

    UserCont friends_;

    UserDatabase* pDB_;

};

void User::AddFriend(User& newFriend)

{

    // Add the new friend to the database

    pDB_->AddFriend(GetName(), newFriend.GetName());

    // Add the new friend to the vector of friends

    friends_.push_back(&newFriend);

}

Surprisingly, the two-liner User::AddFriend hides a pernicious bug. In an out-of-memory condition, vector::push_back can fail by throwing an exception. In that case, you will end up having the friend added to the database, but not to the in-memory information. Now we've got a problem, haven't we? In any circumstance, this inconsistent information is dangerous. It is likely that many parts of your application are based on the assumption that the database is in sync with the in-memory information. A simple approach to the problem is to switch the two lines of code:

void User::AddFriend(User& newFriend)

{

    // Add the new friend to the vector of friends

    // If this throws, the friend is not added to

    //     the vector, nor the database

    friends_.push_back(&newFriend);

    // Add the new friend to the database

    pDB_->AddFriend(GetName(), newFriend.GetName());

}

This definitely causes consistency in the case of vector::push_back failing. Unfortunately, as you consult UserDatabase::AddFriend's documentation, you discover with annoyance that it can throw an exception, too! Now you might end up with the friend in the vector, but not in the database! It's time to interrogate the database folks: "Why don't you guys return an error code instead of throwing an exception?" "Well," they say, "we're using a highly reliable cluster of XYZ database servers on a TZN network, so failure is extremely rare. Being this rare, we thought it's best to model failure with an exception, because exceptions appear only in exceptional conditions, right?" It makes sense, but you still need to address failure. You don't want a database failure to drag the whole system towards chaos. This way you can fix the database without having to shut down the whole server. In essence, you must do two operations, either of which can fail. If either fails, you must undo the whole thing. Let's see how this can be done.

Solution 1: Brute Force

A simple solution is to throw in (sic!) a try-catch block:

void User::AddFriend(User& newFriend)

{

    friends_.push_back(&newFriend);

    try

    {

        pDB_->AddFriend(GetName(), newFriend.GetName());

    }

    catch (...)

    {

        friends_.pop_back();

        throw;

    }

}

If vector::push_back fails, that's okay because UserDatabase::AddFriend is never reached. If UserDatabase::AddFriend fails, you catch the exception (no matter what it is), you undo the push_back operation with a call to vector::pop_back, and you nicely re-throw the exact same exception. The code works, but at the cost of increased size and clumsiness. The two-liner just became a ten-liner. This technique isn't appealing; imagine littering all of your code with such try-catch statements. Moreover, this technique doesn't scale well. Imagine you have a third operation to do. In that case, things suddenly become much clumsier. You can choose between equally awkward solutions: nested try statements or a more complicated control flow featuring additional flags. These solutions raise code bloating issues, efficiency issues, and, most important, severe understandability and maintenance issues.

Solution 2: The Politically Correct Approach

Show the above to any C++ expert, and you're likely to hear: "Nah, that's no good. You must use the initialization is resource acquisition idiom [1] and leverage destructors for automatic resource deallocation in case of failure." Okay, let's go down that path. For each operation that you must undo, there's a corresponding class. The constructor of that class "does" the operation, and the destructor rolls that operation back. Unless you call a "commit" function, in which case the destructor does nothing. Some code will make all this crystal clear. For the push_back operation, let's put together a VectorInserter class like so:

class VectorInserter

{

public:

    VectorInserter(std::vector& v, User& u)

    : container_(v), commit_(false)

    {

        container_.push_back(&u);

    }

    void Commit() throw()

    {

        commit_ = true;

    }

    ~VectorInserter()

    {

        if (!commit_) container_.pop_back();

    }

private:

    std::vector& container_;

    bool commit_;

};

Maybe the most important thing in the above code is the throw() specification next to Commit. It documents the reality that Commit always succeeds, because you already did the work — Commit just tells VectorInserter: "Everything's fine, don't roll back anything." You use the whole machinery like this:

void User::AddFriend(User& newFriend)

{

    VectorInserter ins(friends_, &newFriend);

    pDB_->AddFriend(GetName(), newFriend.GetName());

    // Everything went fine, commit the vector insertion

    ins.Commit();

}

AddFriend now has two distinct parts: the activity phase, in which the operations occur, and the commitment phase, which doesn't throw — it only stops the undo from happening. The way AddFriend works is simple: if any operation fails, the point of commitment is not reached and the whole operation is called off. The inserter pop_backs the data entered, so the program remains in the state it was before calling AddFriend. The idiom works nicely in all cases. If, for example, the vector insertion fails, the destructor of ins is not called, because ins isn't constructed. (If you designed C++, would you have called the destructor for an object whose very construction failed?) This approach works just fine, but in the real world, it turns out not to be that neat. You must write a bunch of little classes to support this idiom. Extra classes mean extra code to write, intellectual overhead, and additional entries to your class browser. Moreover, it turns out there are lots of places where you must deal with exception safety. Let's face it, adding a new class every so often just for undoing an arbitrary operation in its destructor is not the most productive. Also, VectorInserter has a bug. Did you notice it? VectorInserter's copy constructor does very bad things. Defining classes is hard; that's another reason for avoiding writing lots of them.

Solution 3: The Real Approach

It's one or the other: either you have reviewed all the options above, or you didn't have time or care for them. At the end of the day, do you know what the real approach is? Of course you do. Here it is:

void User::AddFriend(User& newFriend)

{

    friends_.push_back(&newFriend);

    pDB_->AddFriend(GetName(), newFriend.GetName());

}

It's a solution based upon not so scientific arguments. "Who said memory's going to exhaust? There's half a gig in this box!" "Even if memory does exhaust, the paging system will slow the program down to a crawl way before the program crashes." "The database folks said AddFriend cannot possibly fail. They're using XYZ and TZN!" "It's not worth the trouble. We'll think of it at a later review." Solutions that require a lot of discipline and grunt work are not very attractive. Under schedule pressure, a good but clumsy solution loses its utility. Everybody knows how things must be done by the book, but will consistently take the shortcut. The one true way is to provide reusable solutions that are correct and easy to use. You check in the code, having an unpleasant feeling of imperfection, which gradually peters out as all tests run just fine. As time goes on and schedule pressure builds up, the spots that can "in theory" cause problems crop up. You know you have a big problem: you have given up controlling the correctness of your application. Now when the server crashes, you don't have a clue about where to start: is it a hardware failure, a genuine bug, or an amok state due to an exception? Not only are you exposed to involuntary bugs, you deliberately introduced them! Life is change. The number of users can grow, stressing memory to its limits. Your network administrator might disable paging for the sake of performance. Your database might not be so infallible. And you are unprepared for any of these.

Solution 4: Petru's Approach

Using the ScopeGuard tool (which we'll explain in a minute), you can easily write code that's simple, correct, and efficient:

void User::AddFriend(User& newFriend)

{

    friends_.push_back(&newFriend);

    ScopeGuard guard = MakeObjGuard(

        friends_, &UserCont::pop_back);

    pDB_->AddFriend(GetName(), newFriend.GetName());

    guard.Dismiss();

}

guard's only job is to call friends_.pop_back when it exits its scope. That is, unless you Dismiss it. If you do that, guard no longer does anything. ScopeGuard implements automatic calls to functions or member functions in its destructor. It can be helpful when you want to implement automatic undoing of atomic operations in the presence of exceptions. You use ScopeGuard like so: if you need to do several operations in an "all-or-none" fashion, you put a ScopeGuard after each operation. The execution of that ScopeGuard nullifies the effect of the operation above it:

friends_.push_back(&newFriend);

ScopeGuard guard = MakeObjGuard(

    friends_, &UserCont::pop_back);

ScopeGuard works with regular functions, too:

void* buffer = std::malloc(1024);

ScopeGuard freeIt = MakeGuard(std::free, buffer);

FILE* topSecret = std::fopen("cia.txt");

ScopeGuard closeIt = MakeGuard(std::fclose, topSecret);

If all atomic operations succeed, you Dismiss all guards. Otherwise, each constructed ScopeGuard will diligently call the function with which you initialized it. With ScopeGuard you can easily arrange to undo various operations without having to write special classes for removing the last element of a vector, freeing some memory, and closing a file. This makes ScopeGuard a very useful reusable solution for writing exception-safe code, easily.

Implementing ScopeGuard

ScopeGuard is a generalization of a typical implementation of the "initialization is resource acquisition" C++ idiom. The difference is that ScopeGuard focuses only on the cleanup part — you do the resource acquisition, and ScopeGuard takes care of relinquishing the resource. (In fact, cleaning up is arguably the most important part of the idiom.) There are different ways of cleaning up resources, such as calling a function, calling a functor, and calling a member function of an object. Each of these can require zero, one, or more arguments. Naturally, we model these variations by building a class hierarchy. The destructors of the objects in the hierarchies do the actual work. The base of the hierarchy is the ScopeGuardImplBase class, shown below:

class ScopeGuardImplBase

{

public:

    void Dismiss() const throw()

    {    dismissed_ = true;    }

protected:

    ScopeGuardImplBase() : dismissed_(false)

    {}

    ScopeGuardImplBase(const ScopeGuardImplBase& other)

    : dismissed_(other.dismissed_)

    {    other.Dismiss();    }

    ~ScopeGuardImplBase() {} // nonvirtual (see below why)

    mutable bool dismissed_;

 

private:

    // Disable assignment

    ScopeGuardImplBase& operator=(

        const ScopeGuardImplBase&);

};

ScopeGuardImplBase manages the dismissed_ flag, which controls whether derived classes perform cleanup or not. If dismissed_ is true, then derived classes will not do anything during their destruction. This brings us to the missing virtual in the definition of ScopeGuardImplBase's destructor. What polymorphic behavior of the destructor would you expect if it's not virtual? Hold your curiosity for a second; we have an ace up our sleeves that allows us to obtain polymorphic behavior without the overhead of virtual functions. For now, let's see how to implement an object that calls a function or functor taking one argument in its destructor. However, if you call Dismiss, the function/functor is no longer invoked.

template <typename Fun, typename Parm>

class ScopeGuardImpl1 : public ScopeGuardImplBase

{

public:

    ScopeGuardImpl1(const Fun& fun, const Parm& parm)

    : fun_(fun), parm_(parm)

    {}

    ~ScopeGuardImpl1()

    {

        if (!dismissed_) fun_(parm_);

    }

private:

    Fun fun_;

    const Parm parm_;

};

To make it easy to use ScopeGuardImpl1, let's write a helper function.

template <typename Fun, typename Parm>

ScopeGuardImpl1<Fun, Parm>

MakeGuard(const Fun& fun, const Parm& parm)

{

    return ScopeGuardImpl1<Fun, Parm>(fun, parm);

}

MakeGuard relies on the compiler's ability to deduce template arguments for template functions. This way you don't need to specify the template arguments to ScopeGuardImpl1 — actually, you don't need to explicitly create ScopeGuardImpl1 objects. This trick is used by standard library functions, such as make_pair and bind1st. Still curious about how to achieve polymorphic behavior of the destructor without a virtual destructor? It's time to write the definition of ScopeGuard, which, surprisingly, is a mere typedef:

typedef const ScopeGuardImplBase& ScopeGuard;

Now we'll disclose the whole mechanism. According to the C++ Standard, a reference initialized with a temporary value makes that temporary value live for the lifetime of the reference itself. Let's explain this with an example. If you write:

FILE* topSecret = std::fopen("cia.txt");

ScopeGuard closeIt = MakeGuard(std::fclose, topSecret);

then MakeGuard creates a temporary variable of type (deep breath here):

ScopeGuardImpl1<int (&)(FILE*), FILE*>

This is because the type of std::fclose is a function taking a FILE* and returning an int. The temporary variable of the type above is assigned to the const reference closeIt. As stated in the language rule above, the temporary variable lives as long as the reference — and when it is destroyed, the correct destructor is called. In turn, the destructor closes the file. ScopeGuardImpl1 supports functions (or functors) taking one parameter. It is very simple to build classes that accept zero, two, or more parameters (ScopeGuardImpl0, ScopeGuardImpl2...). Once you have these, you overload MakeGuard to achieve a nice, unified syntax:

template <typename Fun>

ScopeGuardImpl0<Fun>

MakeGuard(const Fun& fun)

{

    return ScopeGuardImpl0<Fun >(fun);

}

...

We already have a powerful means of expressing automatic calls to functions. MakeGuard is an excellent tool especially when it comes to interfacing with C APIs without having to write lots of wrapper classes. What's even better is the preservation of efficiency, as there's no virtual call involved.

ScopeGuard for Objects and Member Functions

So far, so good, but what about invoking member functions for objects? It's not hard at all. Let's implement ObjScopeGuardImpl0, a class template that can invoke a parameterless member function for an object.

template <class Obj, typename MemFun>

class ObjScopeGuardImpl0 : public ScopeGuardImplBase

{

public:

    ObjScopeGuardImpl0(Obj& obj, MemFun memFun)

    : obj_(obj), memFun_(memFun)

    {}

    ~ObjScopeGuardImpl0()

    {

        if (!dismissed_) (obj_.*fun_)();

    }

private:

    Obj& obj_;

    MemFun memFun_;

};

ObjScopeGuardImpl0 is a bit more exotic because it uses the lesser-known pointers to member functions and operator.*. To understand how it works, let's take a look at MakeObjGuard's implementation. (We availed ourselves of MakeObjGuard in the opening section.)

template <class Obj, typename MemFun>

ObjScopeGuardImpl0<Obj, MemFun, Parm>

MakeObjGuard(Obj& obj, Fun fun)

{

    return ObjScopeGuardImpl0<Obj, MemFun>(obj, fun);

}

Now if you call:

ScopeGuard guard = MakeObjGuard(

    friends_, &UserCont::pop_back);

then an object of the following type is created:

ObjScopeGuardImpl0<UserCont, void (UserCont::*)()>

Fortunately, MakeObjGuard saves you from having to write types that look like uninspired emoticons. The mechanism is the same — when guard leaves its scope, the destructor of the temporary object is called. The destructor invokes the member function via a pointer to a member. To achieve that, we use operator.*.

Error Handling

If you have read Herb Sutter's work on exceptions [2], you know that it is essential that destructors must not throw an exception. A throwing destructor makes it impossible to write correct code and can shut down your application without any warning. The destructors of ScopeGuardImplX and ObjScopeGuardImplX call an unknown function or member function respectively. These functions might throw. In theory, you should never pass functions that throw to MakeGuard or MakeObjGuard. In practice (as you can see in the downloadable code), the destructor is shielded from any exceptions:

template <class Obj, typename MemFun>

class ObjScopeGuardImpl0 : public ScopeGuardImplBase

{

    ...

public:

    ~ScopeGuardImpl1()

    {

        if (!dismissed_)

            try { (obj_.*fun_)(); }

            catch(...) {}

    }

}

The catch(...) block does nothing. This is not a hack. In the realm of exceptions, it is fundamental that you can do nothing if your "undo/recover" action fails. You attempt an undo operation, and you move on regardless whether the undo operation succeeds or not. A possible sequence of actions in our instant messaging example is: you insert a friend in the database, you try to insert it in the friends_ vector and fail, and consequently you try to delete the user from the database. There is a small chance that somehow the deletion from the database fails, too, which leads to a very unpleasant state of affairs. In general, you should put guards on operations that you are the most sure you can undo successfully.

Supporting Parameters by Reference

We were happily using ScopeGuard for a while, until we stumbled upon a problem. Consider the code below:

void Decrement(int& x) { --x; }

void UseResource(int refCount)

{

    ++refCount;

    ScopeGuard guard = MakeGuard(Decrement, refCount);

    ...

}

The guard object above ensures that the value of refCount is preserved upon exiting UseResource. (This idiom is useful in some resource sharing cases.) In spite of its usefulness, the code above does not work. The problem is, ScopeGuard stores a copy of refCount (see the definition of ScopeGuardImpl1, member variable parm_) and not a reference to it. In this case, we need to store a reference to refCount so that Decrement can operate on it. One solution would be to implement additional classes, such as ScopeGuardImplRef and MakeGuardRef. This is a lot of duplication, and it gets nasty as you implement classes for multiple parameters. The solution we settled on consists of a little helper class that transforms a reference into a value:

template <class T>

class RefHolder

{

    T& ref_;

public:

    RefHolder(T& ref) : ref_(ref) {}

    operator T& () const

    {

        return ref_;

    }

};

template <class T>

inline RefHolder<T> ByRef(T& t)

{

    return RefHolder<T>(t);

}

RefHolder and its companion helper function ByRef are ingenious; they seamlessly adapt a reference to a value and allow ScopeGuardImpl1 to work with references without any modification. All you have to do is to wrap your references in calls to ByRef, like so:

void Decrement(int& x) { --x; }

void UseResource(int refCount)

{

    ++refCount;

    ScopeGuard guard = MakeGuard(Decrement, ByRef(refCount));

    ...

}

We find this solution to be pretty expressive and suggestive. The nicest part of reference support is the const modifier used in ScopeGuardImpl1. Here's the relevant excerpt:

template <typename Fun, typename Parm>

class ScopeGuardImpl1 : public ScopeGuardImplBase

{

    ...

private:

    Fun fun_;

    const Parm parm_;

};

This little const is very important. It prevents code that uses non-const references from compiling and running incorrectly. In other words, if you forget to use ByRef with a function, the compiler will not allow incorrect code to compile.

But Wait, There's More

You now have everything you need to write exception-safe code without having to agonize over it. Sometimes, however, you want the ScopeGuard to always execute when you exit the block. In this case, creating a dummy variable of type ScopeGuard is awkward — you only need a temporary, you don't need a named temporary. The macro ON_BLOCK_EXIT does exactly that and lets you write expressive code like below:

{

    FILE* topSecret = fopen("cia.txt");

    ON_BLOCK_EXIT(std::fclose, topSecret);

    ... use topSecret ...

} // topSecret automagically closed

ON_BLOCK_EXIT says: "I want this action to be performed when the current block exists." Similarly, ON_BLOCK_EXIT_OBJ implements the same feature for a member function call.

These macros use non-orthodox (albeit legal) macro wizardry, which shall go undisclosed. The curious can look them up in the code.

ScopeGuard in the Real World

Maybe the coolest thing about ScopeGuard is its ease of use and conceptual simplicity. This article has detailed the entire implementation, but explaining ScopeGuard's usage only takes a couple of minutes. Amongst our colleagues, ScopeGuard has spread like wildfire. Everybody takes ScopeGuard for granted as a valuable tool that helps in various situations, from premature returns to exceptions. With ScopeGuard, you can finally write exception-safe code with reasonable ease and understand and maintain it just as easily.

Every tool comes with a use recommendation, and ScopeGuard is no exception. You should use ScopeGuard as it was intended — as an automatic variable in functions. You should not hold ScopeGuard objects as member variables, try to put them in vectors, or allocate them on the heap. For these purposes, the downloadable code contains a Janitor class, which does exactly what ScopeGuard does, but in a more general way — at the expense of some efficiency.

Conclusion

We have presented the issues that arise in writing exception-safe code. After discussing a couple of ways of achieving exception safety, we have introduced a generic solution. ScopeGuard uses several generic programming techniques to let you prescribe function and member function calls when a ScopeGuard variable exits a scope. Optionally, you can dismiss the ScopeGuard object.

ScopeGuard is useful when you need to perform automatic cleanup of resources. This idiom is important when you want to assemble an operation out of several atomic operations, each of which could fail.

Acknowledgements

The authors would like to thank to Mihai Antonescu for reviewing this paper and for making useful corrections and suggestions.

Bibliography

[1] Bjarne Stroustrup. The C++ Programming Language, 3rd Edition (Addison Wesley, 1997), page 366.

[2] Herb Sutter. Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions (Addison-Wesley, 2000).

Andrei Alexandrescu is a development manager at RealNetworks Inc. (www.realnetworks.com), based in Seattle, WA. He may be contacted at www.moderncppdesign.com.

Petru Marginean is senior C++ developer for Plural, New York. He can be reached at petrum@hotmail.com.

      S T U D Y/C++  |  2008. 7. 4. 10:48



jekalmin's Blog is powered by Daum