Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

»ý¼ºÀÚ¸¦ ¿Ö ¾²´Â°ÅÁÒ?

75 views
Skip to first unread message

정영준

unread,
Jul 13, 2004, 10:36:47 PM7/13/04
to
클래스에서 맴버 변수를 선언한게 private상태일 경우 이를 접근 하는 멤버
함수가 public으로 지정되어 있을때,

생성자는 private로 선언된 멤버변수를 접근자 메소드를 이용하지 않고 바로
접근해서 좋은건가요?

생성자가 어째서 자주 쓰일수 있는지 궁금합니다...

답변 부탁드려요....


Kim, Seungtai

unread,
Jul 14, 2004, 12:10:28 AM7/14/04
to
"정영준"

심각하게 생각하실 필요 없습니다. 있으면 편하니까요. :)

혹시, RAII(Resource Acquisition Is Instantiation)라고 아시나요?
이 용어는 "자원 획득과 동시에 자원에 대한 초기화가 이루어짐"을 나타내는
프로그래밍 이디엄 중의 하나입니다.

이 RAII에 초점을 맞추어 생성자 존재 이유를 설명해 보겠습니다. 아마도
여타의 이유들은 이 그룹에서 활동하시는 다른 분들이 잘 설명해 주실
것이라고 생각되는군요.

RAII는 int, char 등의 기본 형이 아니라, 지금 정 영준 씨께서 문의하고 있는
사용자 정의형에서 특히 중요한 의미를 갖습니다. int, char 등의 기본 형은
어떤 값으로 초기화 될지(혹은, 초기화가 되지 않을지)에 대한 여부가 모두
정의되어 있어 이를 사용하는 측이 그 값을 예측할 수 있는 반면(Predictable),
사용자 정의형에서는 그 값을 예측할 수가 없거든요.

그리고 값을 예측할 수 없다는 것은 그 자원을 활용했을 때 프로그램이
의도대로 수행되지 않을 수 있는 가능성을 내포히기에 심각한 문제를
초래하는 원흉이 됩니다. 따라서 사용자 정의형을 제공하는 측에서는
값을 예측하는 데 도움을 줄 수 있도록 이 타입의 자원을 획득하였을
때 어떤 값으로 초기화가 되는지를 추가 설명하고, 이 타입을 사용하는
사용자 측에서도 각 타입에 따라 고유한 초기화 방법에 대해 이해해야 되는
불편함이 생기죠.

RAII는 이 불편함을 없엘 수 있는 방법입니다. 즉, 자원의 획득과 동시에
초기화를 지향하여 자원의 초기화에 대해 신경쓸 필요가 없도록 하는
것이죠.

아, 더 얘기를 진행하기 전에 먼저 일반적으로 프로그래밍에서 자원을
활용하는 것을 살펴보죠.

1. 자원의 획득
2. 자원의 사용
3. 자원의 해제

보통은 이 순서로 자원을 사용하게 됩니다만, 만일 자원을 획득하였는데
그 자원이 어떤 값으로 차있는지 알 수 없다면 자원의 사용 단계전에
한 번 더 자원의 초기화가 필요하게 됩니다. 아래처럼.

1. 자원의 획득
2. 자원의 초기화: 자원을 예측 가능하게 만듦
3. 자원의 사용
4. 자원의 해제

그러나 이 방법은 자원을 사용하는 측에서도, 자원을 제공하는 측에서도
자원의 초기화에 대해 신경써야하니 복잡합니다. 그래서, RAII는 아래와
같은 해결책을 제공합니다.

1. 자원의 획득과 초기화
2. 자원의 사용
3. 자원의 해제

자원의 획득과 초기화를 한 단계로 만드는 거죠. 그래서 기본형과
마찬가지로 자원을 획득하는 동시에 초기화를 수행함으로써
적어도 자원을 사용하는 측에서는 신경을 쓰지 않아도 사용자 측면의
편의성을 제공하게 됩니다. 물론 타입을 제공하는 측에서도
통일성있는 방법을 제공하게 되니, 어떤 식으로 초기화를 해야할지
고민할 필요가 없어지고요.

생성자는 바로 자원의 획득과 초기화가 동시에 이루어 질 수 있도록
도와주는 언어적인 장치입니다.즉, 생성자는 자원의 획득이 일어나는
순간에 자동으로 호출되는(malloc 등의 방법은 제외) 특별한 멤버함수로써
RAII가 동작할 수 있는 기초적인 초석이 되는 것이지요.

아래의 예를 살펴볼까요? 만일 생성자가 없다면...

// NOT RAII
class A;

{
A a; // 자원의 획득
a.init(); // 자원의 초기화
a.use(); // 자원의 사용
} // 자원의 해제

위와 같은 순서가 되지만, 생성자가 있다면

// RAII
{
A a; // 자원의 획득과 초기화
a.use(); // 자원의 사용
} // 자원의 해제

가 되어, 프로그램이 간략해지고 사용법 역시 기본형과 다를 바가
없기 때문에 통일성있는 자원 활용이 가능해지지요.

그래서 생성자가 필요한 것입니다.

이와 관련하여서 Big3(생성자, 소멸자, 복사 연산자 중 하나가 특별하게
정의되면 셋 모두 특별한 정의가 필요하다는 이디엄) 등의 관련 이디엄들이
있으니 살펴보시면 도움이 될 것 같습니다.

--
S Kim <st...@yujinrobot.com>


ES Kim

unread,
Jul 14, 2004, 2:31:19 AM7/14/04
to
"Kim, Seungtai" <st...@yujinrobot.com> wrote in message
news:cd29lp$udt$1...@news.kreonet.re.kr...

>
> 혹시, RAII(Resource Acquisition Is Instantiation)라고 아시나요?
> 이 용어는 "자원 획득과 동시에 자원에 대한 초기화가 이루어짐"을 나타내는
> 프로그래밍 이디엄 중의 하나입니다.
>

자세하게 잘 설명해 주셨는데 한 가지 잘못 쓰신 게 있어 끼어듭니다. :-)
RAII는 "Resource Acquisition Is Initialization"입니다.

--
김의석
ES Kim


Kim, Seungtai

unread,
Jul 14, 2004, 2:51:28 AM7/14/04
to
"ES Kim" <n...@spam.mail>

> >
> > 혹시, RAII(Resource Acquisition Is Instantiation)라고 아시나요?
> > 이 용어는 "자원 획득과 동시에 자원에 대한 초기화가 이루어짐"을 나타내는
> > 프로그래밍 이디엄 중의 하나입니다.
> >
>
> 자세하게 잘 설명해 주셨는데 한 가지 잘못 쓰신 게 있어 끼어듭니다. :-)
> RAII는 "Resource Acquisition Is Initialization"입니다.


아, 맞네요. 지적 감사드립니다.

크.. Instantiation에 대해 생각하다가 글을 작성해서 실수했나 봅니다. T__T

--
S Kim <st...@yujinrobot.com>


김승범

unread,
Jul 14, 2004, 8:04:25 PM7/14/04
to
Kim, Seungtai wrote:
>
> RAII는 이 불편함을 없엘 수 있는 방법입니다. 즉, 자원의 획득과 동시에
> 초기화를 지향하여 자원의 초기화에 대해 신경쓸 필요가 없도록 하는
> 것이죠.
>
> 아, 더 얘기를 진행하기 전에 먼저 일반적으로 프로그래밍에서 자원을
> 활용하는 것을 살펴보죠.
>
> 1. 자원의 획득
> 2. 자원의 사용
> 3. 자원의 해제
>
> 보통은 이 순서로 자원을 사용하게 됩니다만, 만일 자원을 획득하였는데
> 그 자원이 어떤 값으로 차있는지 알 수 없다면 자원의 사용 단계전에
> 한 번 더 자원의 초기화가 필요하게 됩니다. 아래처럼.
>
> 1. 자원의 획득
> 2. 자원의 초기화: 자원을 예측 가능하게 만듦
> 3. 자원의 사용
> 4. 자원의 해제

1번 "자원의 획득"과 2번 "자원의 초기화: 자원을 예측 가능하게 만듦"이
어떤 점에서 다른 개념인지 잘 이해가 되지 않는데,
좀더 구체적으로 설명해 주시겠습니까?

1번의 "자원"과 2번의 "자원"이 같은 자원을 가리키는 것인가요?

--
김승범

Kim, Seungtai

unread,
Jul 14, 2004, 8:37:22 PM7/14/04
to
"김승범"

> > RAII는 이 불편함을 없엘 수 있는 방법입니다. 즉, 자원의 획득과 동시에
> > 초기화를 지향하여 자원의 초기화에 대해 신경쓸 필요가 없도록 하는
> > 것이죠.
> >
> > 아, 더 얘기를 진행하기 전에 먼저 일반적으로 프로그래밍에서 자원을
> > 활용하는 것을 살펴보죠.
> >
> > 1. 자원의 획득
> > 2. 자원의 사용
> > 3. 자원의 해제
> >
> > 보통은 이 순서로 자원을 사용하게 됩니다만, 만일 자원을 획득하였는데
> > 그 자원이 어떤 값으로 차있는지 알 수 없다면 자원의 사용 단계전에
> > 한 번 더 자원의 초기화가 필요하게 됩니다. 아래처럼.
> >
> > 1. 자원의 획득
> > 2. 자원의 초기화: 자원을 예측 가능하게 만듦
> > 3. 자원의 사용
> > 4. 자원의 해제
>
> 1번 "자원의 획득"과 2번 "자원의 초기화: 자원을 예측 가능하게 만듦"이
> 어떤 점에서 다른 개념인지 잘 이해가 되지 않는데,
> 좀더 구체적으로 설명해 주시겠습니까?

자원의 획득은 메모리를 예로 들었을 시에 "공간에 대한 할당을 받는 것"을
말합니다. 자원 획득의 예는

char *p = new char ;

과 같은 것이 있을 수 있겠네요.

자원의 초기화는 "그 공간에 어떤 값을 쓰는 것"으로 모든 비트를 0으로
초기화를 하거나, 특정한 형식으로 해석될 수 있는 의미있는 값으로 채우는
것을 말합니다. 초기화의 예에는

*p = 1;

과 같은 것이 있겠고요.

초기화되어 있지 않은 자원은 할당을 받았다 할지라도 사용할 수 없다는
특징이 있죠. 값이 예측 불능하니까요. 그래서 자원을 초기화하는 역할이
필요하고, 이것이 자원을 예측 가능하게 만든다는 뜻에서 쓴 말이었습니다.

그리고, 마지막의 자원의 획득과 초기화가 동시에 이루어지는 예는

char *p = new char(1);

정도가 되겠군요.

뭔가 이상한 점이 있나요?? 지적 부탁드립니다.

>
> 1번의 "자원"과 2번의 "자원"이 같은 자원을 가리키는 것인가요?

--
S Kim <st...@yujinrobot.com>


Chong-Dae Park

unread,
Jul 14, 2004, 11:22:48 PM7/14/04
to
Kim, Seungtai <st...@yujinrobot.com> wrote:
>> 1번 "자원의 획득"과 2번 "자원의 초기화: 자원을 예측 가능하게 만듦"이
>> 어떤 점에서 다른 개념인지 잘 이해가 되지 않는데,
>> 좀더 구체적으로 설명해 주시겠습니까?
>
> 자원의 획득은 메모리를 예로 들었을 시에 "공간에 대한 할당을 받는 것"을
> 말합니다. 자원 획득의 예는
>
> char *p = new char ;
>
> 과 같은 것이 있을 수 있겠네요.
>
> *p = 1;
>
> 과 같은 것이 있겠고요.
>
> 초기화되어 있지 않은 자원은 할당을 받았다 할지라도 사용할 수 없다는
> 특징이 있죠. 값이 예측 불능하니까요. 그래서 자원을 초기화하는 역할이
> 필요하고, 이것이 자원을 예측 가능하게 만든다는 뜻에서 쓴 말이었습니다.
>
> 그리고, 마지막의 자원의 획득과 초기화가 동시에 이루어지는 예는
>
> char *p = new char(1);
>
> 정도가 되겠군요.
>
> 뭔가 이상한 점이 있나요?? 지적 부탁드립니다.

*p의 내용을 다루셨군요. p 포인터 자체에 대해서 생각하면 더 간단합니다.

char *p;
라고 선언하면 우리는 p라는 포인터자원을 획득한 겁니다.
그런데 이 p 값이 초기화되어 있지 않죠?

프로그램 중간에서 p를 만났습니다.
p 값을 살펴보니 p = 0xc3f4eac8 값을 가지고 있네요.
과연 char a = *p; 이라는 식이 무사히 수행될 수 있을까요?

선언때에 char *p = NULL; 혹은 char *p = "hello"; 식으로 초기화가
했다면 우리는 프로그램 중간에 p 값이 NULL 혹은 정상적인 pointer 둘
중의 하나라고만 가정하면 됩니다. 초기화되지 않은 p 값에 대한 걱정을 일단
덜 수 있죠.

--
박종대
--
Warning: Your signature is longer than 4 lines. Since signatures usually do
not transport any useful information, they should be as short as
possible. - tin's Warning message -

김승범

unread,
Jul 15, 2004, 2:18:09 AM7/15/04
to
정영준 wrote:
>
> 생성자가 어째서 자주 쓰일수 있는지 궁금합니다...

쉽게 말하면 생성자는 개체를 초기화하기 위해 필요한 것이지요. :)

그런데 왜 초기화를 하기 위해 init()과 같은 일반 멤버 함수를 사용하지
않고 대신 생성자라는 특별한 것을 사용해야 하는가가 궁금하실 듯한데,
일반 멤버 함수의 경우 사용자가 알아서 호출해 주어야 하고 실수로 호출하지
않더라도 그러한 실수를 컴파일러가 잡아낼 수 없지만, 생성자는 알아서,
또한 강제로 호출되어 invariant를 확립하는 데에 중요한 역할을 합니다.

클래스는 각자 invariant라는 것을 갖게 마련입니다. invariant는 클래스의
개체가 올바로 동작하기 위해 늘 만족하는 성질을 뜻하는 개념입니다.
클래스는 한편으로는 invariant에 의존하여 행동하며, 다른 한편으로는
invariant를 유지하기 위한 조치를 취하여야 하는데, 여기서 생성자는
개체를 초기화하면서 초기의 invariant를 설정하는 중요한 역할을 합니다.

좀 더 자세히 설명하자면, 예컨대

class fraction
{
int numerator, denominator; // denominator is non-zero
// ....
};

이러한 클래스에서 fraction 클래스의 설계자는 denominator가 0이 될 수
없다는 것을 이 클래스의 invariant로 설계할 수 있습니다. 이러한 경우
클래스는 한편으로는 이러한 invariant에 의존하여(depend upon) 행동할 수
있습니다.

double fraction::as_double() const
{
return static_cast<double>(numerator) / denominator;
}

만약 그러한 invariant가 없다면, denominator가 0이 아닌지 검사하는 부분이
다음과 같이 반드시 있어서 0으로 나누는 것을 방지해야 할 것입니다.

double fraction::as_double() const
{
if (denominator != 0)
return static_cast<double>(numerator) / denominator;
else
throw divide_by_zero();
}

하지만 denominator가 non-NULL이라는 invariant가 있기 때문에 이러한
검사를 생략할 수 있는 것이지요.

다른 한편으로는, 클래스는 invariant를 유지하여야(enforce) 합니다.
내부적으로도 이를 깨지 않을 뿐만 아니라, 외부에서 이를 깨려는 시도를
할 경우에도 이를 막아내야 합니다. 그렇지 않아서 invariant가 무너질 수
있으면 이에 의존할 수 없으니까요.

void fraction::set(int n, int d)
{
if (d != 0)
numerator = n, denominator = d;
else
throw invalid_argument();
}

위의 예에서 보면 fraction::set은 denominator가 0이 아니라는 invariant를
지키기 위한 조치를 취하고 있습니다. 외부에서 fraction::set(0, 0) 등으로
invariant를 깨려고 시도하여도 이를 거부하고 invariant를 유지합니다.
클래스의 데이터 멤버를 private으로 설정하고, 멤버 함수만 public으로
설정하여 데이터 멤버에 접근하려면 반드시 멤버 함수를 통하게 하는 것도
이와 같이 invariant를 유지하게 하기 위한 점이 크게 작용합니다.

생성자는 클래스 invariant를 처음 확립하는 역할을 합니다. 만약 생성자가
없고 사용자가 알아서 멤버 함수를 호출하여 초기화하게 되어 있다면, 그렇게
초기화를 하기 전에는 invariant가 지켜질 수 없습니다. 이러한 상태에서
invariant에 의존하는 코드가 실행되면 결과를 보장할 수 없겠지요.

fraction f;
f.as_double(); // Oops, f.denominator might be zero!

하지만

fraction::fraction(int n = 0, int d = 1) { set(n, d); }

위와 같은 생성자가 있음으로 해서

fraction f(1, 0);

과 같이 invariant를 깨는 fraction 개체를 생성하는 것이 불가능하고,

fraction f;

와 같이 인자가 없이 fraction 개체를 생성하더라도 반드시 생성자가
호출되거나 또는 기본 생성자가 없는 경우 진단 메시지가 발생하게 하여,
fraction 개체는 일단 생성되기만 하면 invariant를 반드시 만족하는 상태가
된다는 것을 보장할 수 있는 것입니다.

Kim, Seungtai 님께서 말씀하신 "자원의 획득과 초기화" 문제도 넓게 보면
invariant의 문제라고 할 수 있습니다. 즉 자원을 획득하되 초기화를 하지
않으면 invariant가 유지되지 않는 경우, 생성자를 통하여 초기화를 강제로
수행함으로써 늘 invariant가 유지되도록 할 수 있는 것입니다.

--
김승범

김승범

unread,
Jul 15, 2004, 8:55:44 PM7/15/04
to
Kim, Seungtai wrote:

> 이 RAII에 초점을 맞추어 생성자 존재 이유를 설명해 보겠습니다. 아마도
> 여타의 이유들은 이 그룹에서 활동하시는 다른 분들이 잘 설명해 주실
> 것이라고 생각되는군요.
>

> [...]


>
> 생성자는 바로 자원의 획득과 초기화가 동시에 이루어 질 수 있도록
> 도와주는 언어적인 장치입니다.즉, 생성자는 자원의 획득이 일어나는
> 순간에 자동으로 호출되는(malloc 등의 방법은 제외) 특별한 멤버함수로써
> RAII가 동작할 수 있는 기초적인 초석이 되는 것이지요.

Kim, Seungtai wrote:

> 그리고, 마지막의 자원의 획득과 초기화가 동시에 이루어지는 예는
>
> char *p = new char(1);
>
> 정도가 되겠군요.

어떤 자원을 획득함과 동시에 그 자원을 초기화한다는 의미로 RAII라는
용어를 사용하셨군요. 하지만 이는 일반적으로 사용되는 의미로서의
RAII는 아닙니다.

RAII의 원래 의미는, 자원을 클래스로 표현하여 그 클래스의 개체 생성시
자원을 획득하고 개체 소멸시 자원을 반환함으로써 자원의 관리를 체계적이고
용이하게 하는 기법입니다. RAII라는 이름에서는 초기화만을 언급하고 있지만
사실 초기화보다는, 자원 반환이 소멸자에 의해 자동으로 이루어진다는 것이
RAII의 더 중요한 측면입니다. 함수 중간에서 return 문이나 예외를 통해서
함수를 빠져나가더라도, 자동 개체가 stack unwinding 과정에서 소멸되면서
소멸자를 통해서 자원의 반환이 자동으로 일어나게 되어 자원 반환에 특별히
신경을 쓸 필요가 없는 것이지요. 물론 이런 방법을 안전하게 쓰려면
생성자에서 초기화를 잘 해주는 것이 뒷받침이 되어야 하겠습니다.

RAII 기법의 예는 TC++PL3 14.4절 및 아래 웹페이지에서 보실 수 있습니다.
http://www.research.att.com/~bs/bs_faq2.html#finally

--
김승범

Kim, Seungtai

unread,
Jul 15, 2004, 9:47:21 PM7/15/04
to
"김승범"

네, 개념적인 본 바탕은 김 승범 씨께서 지적하신 사안이 맞습니다.

RAII 자체에 대해서는 아래 URL에서 좀 더 자세하게 설명하고 있으니,
참고하세요.

http://sourceforge.net/docman/display_doc.php?docid=8673&group_id=9028
http://www.hackcraft.net/raii/

이런 프로그래밍 이디엄들에 대해 잘 정리해 놓은 책이나 사이트가 있는지
모르겠군요? 전부터 찾아보고 있는데 적극적으로 찾지 않아서인지 눈에
잘 안띄는데, 혹시 누가 아신다면... :-)

--
S Kim <st...@yujinrobot.com>


0 new messages