C++ 에서 생성자를 호출할 때 예외 처리는 일반적으로 어떻게 처

vudghkzm의 이미지

생성자를 호출할 때 예외가 발생할 소지가 있을때, 이 예외를 일반적으로 어떻게 처리해주나요?

예를 들면, 생성자를 호출할 때 넘겨주는 인자가 올바른 인자인지 아닌지를 판단해서 올바르지 않으면 뭔가 리턴 값이나 그런것들을 리턴해서 알려줘야 할텐데... 생성자에 있어서는 어떻게 해야 될지 모르겠습니다. 일반 함수라면 그냥 리턴값으로 어찌 처리를 할텐데 말이죠..

어떻게 해야 하나요?

Necromancer의 이미지

내부에 성공여부를 알리는 변수와 그걸 체크하는 메소드를 만들어놓고
생성 직후 호출하는 방법이 많이 쓰이지 않나요?

Written By the Black Knight of Destruction

bugiii의 이미지

if( failed )
    throw some_exception;
vudghkzm의 이미지

두번째 분 말씀대로 exception 처리를 해주면 좋겠네요.

근데 자세하게 어떤 방법으로 하면 되는지 잘 모르겟네요.
자바와는 문법, 형식이 다른거 같아서 말이죠..

doldori의 이미지

문법이야 책을 보면 쉽게 알 수 있을 텐데요. 간단한 예입니다.

struct S
{
    struct Error { };
    S(int i = 0) { if (i < 0) throw Error(); }
};

#include <iostream>

int main()
{
    try
    {
        S* p = new S(-1);
    }
    catch (const S::Error& e)
    {
        std::cerr << "oops\n";
    }
}
doldori의 이미지

Necromancer wrote:
내부에 성공여부를 알리는 변수와 그걸 체크하는 메소드를 만들어놓고
생성 직후 호출하는 방법이 많이 쓰이지 않나요?

매우 좋지 않은 방법입니다. 그런 식으로 하면 코드가 쓸데없이 복잡해집니다. 예를 들어
class C
{
public:
    C(int i) : i_(i), valid_(i_ >= 0) { }
    bool isValid() const { return valid_; }
    void f()
    {
         if (valid_) /* ... */;
         else /* ... */;
    }
    void g()
    {
         if (valid_) /* ... */;
         else /* ... */;
    }
private:
    int i_;
    bool valid_;
};

이렇게 일일이 if-else 등으로 검사를 해야겠지요. 클래스의 invariant를 만족하지
않을 때는 생성자에서 예외를 던져서 아예 생성이 안되도록 해야 합니다.
vudghkzm의 이미지

어디서 들은 내용입니다만 VC++ 6에서는 함수 차원에서의 Exception은 문법상 허용이 안된다고 알고 있는데 사실인가요?

그렇다면 VC++ 6에서는 생성자에서 발생하는 예외는 따로 InitInstance() 와 같은 메쏘드를 만들고 이 메쏘드를 통해서 객체를 초기화 시키고, 그에 따라 발생하는 예외를 처리해줘야 한느 것인가요?

버려진의 이미지

vudghkzm wrote:
어디서 들은 내용입니다만 VC++ 6에서는 함수 차원에서의 Exception은 문법상 허용이 안된다고 알고 있는데 사실인가요?

그렇다면 VC++ 6에서는 생성자에서 발생하는 예외는 따로 InitInstance() 와 같은 메쏘드를 만들고 이 메쏘드를 통해서 객체를 초기화 시키고, 그에 따라 발생하는 예외를 처리해줘야 한느 것인가요?

Game Programming Gems 1권에 나온 내용 같네요. C++은 기초중에 기초밖에 몰라서 뭐라고 말할 수는 없지만... 제 기억에는 생성자는 리턴값을 가질 수 없어서 성공 실패 여부를 가릴 수 없으니, 생성자에서 다른 함수를 호출하는 식으로 초기화를 해라.. 라는 내용이었던 것 같습니다.

rasungboy의 이미지

doldori wrote:
Necromancer wrote:
내부에 성공여부를 알리는 변수와 그걸 체크하는 메소드를 만들어놓고
생성 직후 호출하는 방법이 많이 쓰이지 않나요?

매우 좋지 않은 방법입니다. 그런 식으로 하면 코드가 쓸데없이 복잡해집니다. 예를 들어
class C
{
public:
    C(int i) : i_(i), valid_(i_ >= 0) { }
    bool isValid() const { return valid_; }
    void f()
    {
         if (valid_) /* ... */;
         else /* ... */;
    }
    void g()
    {
         if (valid_) /* ... */;
         else /* ... */;
    }
private:
    int i_;
    bool valid_;
};

이렇게 일일이 if-else 등으로 검사를 해야겠지요. 클래스의 invariant를 만족하지
않을 때는 생성자에서 예외를 던져서 아예 생성이 안되도록 해야 합니다.

해당 클래스의 모든 메쏘드에 성공적인 생성자 호출에 대한

검사를 일일히 할 필요가 있을까요?

그냥 한번만 valid_ 가 성공인지 실패인지만 알아보고

실패면 해당 클래스의 메쏘드를 사용하지 않으면 될것

이고 성공이면 사용하면 되는것이고요.

네크로멘서님의 의도가 제가 말한것과 같은 의도

인것 같은데 말이죠..

doldori의 이미지

rasungboy wrote:
해당 클래스의 모든 메쏘드에 성공적인 생성자 호출에 대한

검사를 일일히 할 필요가 있을까요?

그냥 한번만 valid_ 가 성공인지 실패인지만 알아보고

실패면 해당 클래스의 메쏘드를 사용하지 않으면 될것

이고 성공이면 사용하면 되는것이고요.


그런 방법이 좋지 않은 이유는 몇 가지가 있습니다.

1. 유효하지 않은 개체가 생성된다는 것 자체가 논리적이지 않습니다. 살아 있되
살아 있지 않은 상태라고나 할까요.

2. 예를 들어 그런 클래스 C 개체를 멤버로 갖는 클래스 D가 있다고 합시다. 그러면
D 개체도 유효하지 않을 수 있으므로 D::isValid() 같은 함수를 또 정의해야 합니다.
너무 불편하지 않나요?

3. 위의 이유에 비하면 아주 사소하지만 모든 개체가 valid_라는 멤버를 갖고 있는
것은 메모리 낭비입니다. 또한 개체를 생성할 때마다 유효한 개체인지 검사하는
것도 비효율적입니다.

bugiii의 이미지

개인적으로 doldori 님의 말씀처럼 생성자에서 어떤 오류가 있다면 그 객체 자체를 생성하지 않는 방법을 가능한한 취하고 있습니다만, 이것만을 고집하기에는 현실적으로 조금 고통스러운 면이 없지 않아 있습니다. 장난스럽게 질문에 답변을 달긴했지만 그것이 텍스트들에서 일반적으로 제시하는 생성자의 에러는 예외로 처리해야만 한다는 것이지 현업에서 그것만을 가지고 생성자에서의 오류를 처리하기에는 조금 무리가 따른다는 경우가 있다고 생각합니다.

예를 들자면, 파일을 객체로 처리하는 클래스가 있고 이것의 생성자에서만 파일을 오픈하는 경우가 있다고 합시다. 보통 파일의 오픈시에 오류는 너무나 일반적이어서 이것의 오류를 예외로 처리하기에는 조금 곤란한 경우가 있지 않을까 합니다.

예외라?것이 하부의 (가끔 일어는, 정말) 예외적인 상황을 최상위 어플리케이션 레벨로 전파하기 위한 것이라면, 이러한 일반적인 오류에 예외를 사용하는 것은 다른 것은 제처놓더라도 사용자 입장에서 그 일반적인(!) 오류를 처리하기 위해서 try/catch 문장을 지역적으로 사용해야 한다는 것에 부담을 느낄 것이라는 것이라는 것입니다.

그래서인지는 몰라도 c++ 의 fstream 클래스들은 생성자에서 파일 이름을 받기도 하고 open 이라는 멤버에서도 파일 이름으로 파일을 오픈할 수 있도록하고 그 처리결과를 ! 연산자 오버로딩을 통해서 오류를 체크하도록 하고 있습니다. 물론, 이런 경우 에러인지를 보관하는 플래그를 저장해야 하는 문제와 모든 멤버의 시작부분에 에러가 있었는지를 조사해야하는 부담은 당연히 따라오게 됩니다.

이 두가지 스타일의 선택은 그 클래스의 용도가 어떠한가에 의해서 결정되어야 한다고 생각합니다. 저도 doldori 님의 말씀대로 생성자에서 예외의 사용은 합당하고도 이상적인 선택이라고 생각하지만 현실적으로 현업에서의 적용이 그리 만만한 것이 아니었다는 것이 고민이라고 할까요? 누군가가 그랬었는데, 예외는 던지기는 쉬워도 받기는 힘들다고 합니다. 또 예외는 그냥 얻어지는 것이 아니라 프로젝트 전체에 걸쳐서 면밀히 검토되어야 하고, 도입하려고 한다면 하부 라이브러리와 어플리케이션 모두 예외에 잘 대처하고 사용해야 한다고 배웠습니다. (주제가 예외 자체로 번질 것 같아서 이정도에서...)

마지막으로 rasungboy 님의 글에 대한 제 생각은 그 클래스를 만든 사람이라면 문제가 없겠지만 그 클래스를 사용하는 사람은 생성자의 오류상황을 검사하지 않고 멤버를 호출할 수 있기 때문에 잘못될 경우가 있을 수 있다고 생각합니다. 안전한 것이 우선이라고 생각합니다.

doldori의 이미지

bugiii wrote:
개인적으로 doldori 님의 말씀처럼 생성자에서 어떤 오류가 있다면 그 객체 자체를 생성하지 않는 방법을 가능한한 취하고 있습니다만, 이것만을 고집하기에는 현실적으로 조금 고통스러운 면이 없지 않아 있습니다. 장난스럽게 질문에 답변을 달긴했지만 그것이 텍스트들에서 일반적으로 제시하는 생성자의 에러는 예외로 처리해야만 한다는 것이지 현업에서 그것만을 가지고 생성자에서의 오류를 처리하기에는 조금 무리가 따른다는 경우가 있다고 생각합니다.

동의합니다. 대부분의 가이드라인은 일반적인 상황에 대한 것이지 항상 그것이
적용 가능하거나 적절하지는 않은 것이 사실입니다. ("One size does not fit all.")
다만 유효하지 않은 개체의 생성을 허용하는 스타일이 일반적이지 않다는 점을
말씀드리고자 했습니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.