[c++] if문 사용 줄이기

skek2875의 이미지

안녕하세요. if문을 줄이려고 하는 도중에 막히는 부분이 생겨서 질문 드립니다.

void  Proj::Func1(int type)
{
  if (type == A)
    A_test();
  else if (type == B)
    B_test();
  else if (type == C)
    C_test();
  else if (type == D)
    D_test();
}
 
void  Proj::Func2(int type)
{
  if (type == A)
    A_print();
  else if (type == B)
    B_print();
  else if (type == C)
    C_print();
  else if (type == D)
    D_print();
}

Proj 내부의 모든 함수에는 위 예의 Func1, Func2와 같이 type을 비교해야하는 if문이 있는 상태입니다.
이런 if문을 줄이려고 A, B, C, D 각각 클래스로 분리시켜서 전달인자로 type만 넘겨주면 알아서 해당 type의 클래스를 호출하도록 만들고 싶습니다.

void* CallFunc(int type);

위와 같이 선언된 함수를 Func1, Func2 함수가 호출하면,

void  Proj::Func1(int type)
{
 (CallFunc(type))->test();
}
 
void  Proj::Func2(int type)
{
  (CallFunc(type))->print();
}

위와 같은 형태가 되고 각 클래스의 멤버함수를 호출하도록 만들면 되겠구나 생각했습니다.
문제는 CallFunc함수의 구현인데요...
일단 다음과 같이 구현했습니다.

void* Proj::CallFunc(int type)
{
  if (type == A)
    return m_pA;
  else if (type == B)
    return m_pB;
  else if (type == C)
    return m_pC;
  else if (type == D)
    return m_pD;
}

이렇게 단순히 각 클래스에 대한 객체의 포인터를 return하면 그 객체의 멤버함수를 호출할 수 있을 것이라고 생각했는데 위와 같이 코딩해도 결국에는 Func1, Func2에서 CallFunc를 원하는 클래스로 형변환 시켜야 하더라구요...
아직 기초가 많이 부족해서 그러는데 이를 해결할 수 있는 방법이 있을까요?

익명 사용자의 이미지

그렇게 할 수 있는 방법은 많죠. 함수 포인터를 활용한다던가.

보아하니 C++같은데 C++답게 다형성과 가상 함수로 해결합시다.

이렇게!

#include <iostream>
#include <stdexcept>
using namespace std;
 
class Proj{
private:
    class Func{
    public:
        const char Type;
 
        Func(char t):Type(t){ return; }
        virtual void test() const =0;
        virtual void print() const =0;
    };
 
    class Func_A : public Func{
    public:
        Func_A():Func('A'){ return; }
        virtual void test() const{ cout << "A_test" << endl; }
        virtual void print() const{ cout << "A_print" << endl; }
    };
 
    class Func_B : public Func{
    public:
        Func_B():Func('B'){ return; }
        virtual void test() const{ cout << "B_test" << endl; }
        virtual void print() const{ cout << "B_print" << endl; }
    };
 
    class Func_C : public Func{
    public:
        Func_C():Func('C'){ return; }
        virtual void test() const{ cout << "C_test" << endl; }
        virtual void print() const{ cout << "C_print" << endl; }
    };
 
    class Func_D : public Func{
    public:
        Func_D():Func('D'){ return; }
        virtual void test() const{ cout << "D_test" << endl; }
        virtual void print() const{ cout << "D_print" << endl; }
    };
public:
    enum class type_t : int{ A, B, C, D };
    const Func &CallFunc(type_t type){
        switch(type){
            case type_t::A: { static Func_A a; return a; }
            case type_t::B: { static Func_B b; return b; }
            case type_t::C: { static Func_C c; return c; }
            case type_t::D: { static Func_D d; return d; }
            default: throw domain_error("Proj::CallFunc");
        }
    }
    void Func1(type_t type){ (CallFunc(type)).test(); }
    void Func2(type_t type){ (CallFunc(type)).print(); }
};
 
int main(){
    Proj p;
    p.Func1(Proj::type_t::A);
    p.Func1(Proj::type_t::B);
    p.Func1(Proj::type_t::C);
    p.Func1(Proj::type_t::D);
    p.Func2(Proj::type_t::A);
    p.Func2(Proj::type_t::B);
    p.Func2(Proj::type_t::C);
    p.Func2(Proj::type_t::D);
    return 0;
}

익명 사용자의 이미지

추신

C++를 쓰고 있다면, 뭔가 굉장히 로우레벨적인 일(운영체제로부터 raw memory를 할당받아서 사용한다던가)을 하는 상황이 아닌 한, 코드에 void *가 나타났다는 건 별로 좋은 상황이 아닙니다.

뭔가 말도 안 되는 일을 시도하고 있는 게 아닐까 자문하시고, 언어 차원에서 권장되는 더 바람직한 방법을 찾아야 합니다.

skek2875의 이미지

답변 덕분에 문제를 해결해나가고 있는 중입니다 ㅎㅎ
직접 예제까지 올려주셔서 설명해주셔서 감사합니다.

아이온@Naver의 이미지

우선 runtime의 binding을 이용하시려면, int type 대신에 print/test를 override하는 객체 자체의 포인터를 넘기는 게 좋습니다. 그게 안 되고 있는 것이 문제의 시작인 것 같네요. c++은 runtime이든 compile time이든 binding을 할 때 parameter의 type으로 뭐가 뭔지 구별을 하게 되는 셈인데 지금은 int 타입만 들어가니까 switch-case, if, 혹은 table look-up 중 하나는 피하기 어려워요.

가능하면 int type을 넘기는 대신, 예컨대 익명님의 예에서는 Func 자체를 넘길 수 있도록 디자인을 수정해 보시는 게 한 방법이라고 생각합니다.

굳이 int type을 넘기셔야 한다면, switch는 피할 수 없습니다. 이 경우에는 dynamic binding은 좀 오버킬이라는 생각이 드네요. 그냥 if-else를 switch로 바꾸는 것으로도 충분하다고 생각합니다.

변명의 이미지

Func를 넘길 수 있도록 디자인을 수정하는 건 좋은데... 그럼 Func 객체는 누가 만들죠?

type이 사용자나 네트워크, 혹은 스토리지로부터 오는 값이라면, 결국 어디선가 int type 값을 Func class hierarchy로 옮겨 주는 코드가 필요할 것입니다. 꼭 dynamic binding이 아니라고 해도 비슷한 역할의 코드가 있어야 하는 건 마찬가지겠지요. 저는 질문에서의 CallFunc 함수 아이디어를 좀 더 구체화시켰을 뿐입니다. (근데 왜 static method로 만들지 않았었는지는 모르겠네요. 깜빡한 것 같습니다.)

it-else/switch-case/table lookup 중 어느 것을 쓰는지는 크게 중요할 것 같지 않는데, 제 경험상 Func1, Func2 혹은 다른 곳에서 각각 따로 switch를 쓰는 것보다는, 차라리 CallFunc 안에 스위치 하나를 만들어 두고 가상함수나 함수 포인터 같은 방법으로 해결해버리는 편이 좋은 것 같습니다. 코드 중복은 언제든 좋지 않으니까요. 특히 나중에 case가 추가될 수 있다고 하면 고쳐야 하는 곳을 최대한 줄이는 게 좋지 않겠어요?

아이온@Naver의 이미지

질문 자체에 이 코드가 들어갈 맥락이 거의 안 나와 있으니 별로 생산적인 논쟁이 될 것 같아 보이진 않습니다.

제 얘기는 "int type을 사용하는 것을 피할 수 있다면" 코드가 더 간결해질 거라는 얘기였고, 없다면 어차피 switch-case, if 또는 table look-up 같은 것을 피할 수 없다는 거였죠. 어딘가에서 type을 int나 enum 같은 걸로 꼭 써야 하는 상황이거나 안 쓰자면 개발 비용이 지나치게 추가되어야 한다면, 말씀하신 대로 "비슷한 역할을 하는 코드가 있어야 하는 건 마찬가지"겠지요.

if-else, switch-case, 그리고 table look up의 경우 컴파일러 최적화와 생성된 코드의 성능 차이는 조금 있긴 있겠습니다. 그러나 이 예제만 보면, 어딘가 인라인 되어 핫 룹에 돌지 않는 한 그리 큰 차이는 아니겠고요. 그보다 저는 개인적으로 연쇄적인 if-else를 보는 게 이상하게 괴로워요. 질문도 if-else를 피하자면 어떻게 하냐는 거였으니 제 답변은 이 정도로 간단한 예라면 그냥 switch-case를 쓰시면 되지 않겠냐는 거죠.

마지막 단락은 동의하는데요, 마찬가지로 질문하신 분이 이 코드를 사용하게 될 맥락에 따라 달라질 문제라고 생각합니다. 별로 변화가 없고 메써드도 몇 개 되지 않는다면 오버킬일 수도 있고, 말씀대로 소프트웨어 라이프 사이클에서 자주 변하고 이런 방식으로 사용할 메써드가 많다면 그렇지 않겠지요. 제가 오버킬 아니냐고 한 건, 질문 속의 예제가 매우 단순한 데 비해서 답변의 코드는 상당히 길어서 꺼낸 얘기였습니다.

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.