[C++] 비가상 함수 인터페이스 (NVI)에 대한 질문

GunSmoke의 이미지

EC++ 읽고 있는데요. 항목 35에서 NVI라는 것이 등장합니다. 가상 함수를 private 멤버로 두는 형태로 두고 사용자는 public 비가상 멤버 함수를 통해 이 함수를 호출하는 방법을 의미합니다.

NVI의 예제입니다. healthValue를 public 멤버 함수로 두되 비가상 함수로 선언하고 실제 동작을 맡은 private 가상 함수를 호출하는(doHealthValue) 식으로 구성되어 있습니다.

class GameCharacter {
public :
  int healthValue() const                 //D클래스는 이 함수를 재정의할 수 없음
  {
    ...  //사전동작
    int retVal = doHealthValue();
    ...  //사후동작
    return retVal;
  }
...
private :
  virtual int doHealthValue() const        //D클래스는 이 함수를 재정의할 수 있음
  {
    ...             //캐릭터 체력치 계산을 위한 알고리즘 구현
  }
};

이렇게 하면 가상함수가 호출되기 전에 사전, 사후 동작을 끼워놓을 수 있다는 장점이 있다고 합니다.

GameCharacter 클래스를 P클래스로 이렇게 꾸며놓았으니 실재 여러 캐릭터들이 이 클래스를 상속해서 D 클래스로 만들어야 할 것입니다. 각 클래스에서는 GameCharcter 클래스의 private 함수 doHealthValue를 재정의해서 사용해야겠죠? 저는 이 점이 궁금합니다. D 클래스에서는 private 멤버에 액세스 조차 할 수 없건만 어떻게 재정의한 다음에 호출할 수 있다는 말입니까?
D 클래스에서 doHealthValue를 직접 호출할 수는 없지만 public 함수 HealthValue를 통해 호출하면 되는것인가요? 그럼 HealthValue를 호출하면 D 클래스에서 재정의된 doHealthValue가 호출된다는 말입니까?

실재로 어떻게 호출하는지 간단한 코드로 보여주시면 쉽게 이해할 수 있을 것 같아요.

thyoo의 이미지

GoF가 말하는 Template Pattern하고 같아 보입니다.
doHealthValue()를 private으로 두면 Override가 안되니까
protected의 오기가 아닐까 생각합니다.

private virtual 함수는 특수한 용도(구체 클래쓰 은폐)외에는
못봤읍니다.
___________________________________
Less is More (Robert Browning)

___________________________________
Less is More (Robert Browning)

bugiii의 이미지

멤버함수 재정의와 멤버의 공개여부는 전혀 상관이 없습니다.

thyoo의 이미지

말씀하신 대로 제가 잘못 알고 있었군요.

C++에서 처음으로 실망했습니다.

11.6
The access rules for a virtual function are determined by its declaration and are not affected by the rules for a function that later overrides it.
Access is checked at the call point using the type of the expression used to denote teh object for which the member function is called. The access of the member function in the class in which it was defined is in general not known.

In C++, private only restricts access, it does not restrict visibility in a subclass. With private , it is still possible to redefine a private virtual function from a base class in a subclass. This is not a problem, but you cannot prevent redefinition in a subclass, as you can with the Eiffel frozen mechanism.

In Java you cannot override a private method, but you can overload it.
___________________________________
Less is More (Robert Browning)

___________________________________
Less is More (Robert Browning)

bugiii의 이미지

D 클래스에서 doHealthValue 를 재정의하게되면, D 로 만든 객체로 healthValue 를 호출할 때 D 의 doHealthValue 가 호출됩니다.

D 클래스에서 C 클래스의 doHealthValue 를 호출할 수 없는 것은 자명한 일이지만, 이렇게 사용하는 목적이 구현과 인터페이스를 분리하는 것이므로, 구현을 재정의하는 목적에 부합되는 곳에 사용하면 좋은 패턴이라고 생각하고 있습니다.

문제는 D 의 doHealthValue 에서 C 의 doHealthValue 구현을 이용하는 것이라 봅니다. D 로 만든 객체로는 순수한 C 의 test ( 즉 C::doHealthValue 를 호출하는 ) 를 사용할 수 없고 이렇게 했다가는 무한 루프에 빠지게 됩니다. 이런 경우는 특별히 C 의 doHealthValue 를 protected 로 하여 파생된 클래스에서만 사용하도록 하면 될 것입니다.

간단하게 C, D1, D2 등을 만드셔서 테스트 해보시면 답은 바로 나오리라 생각합니다.

그 책의 다음 버전인 Exceptional C++ Style 을 보시면 더 자세한 설명을 보실 수 있습니다. 왜 이렇게 사용하는 것이 좋은가를 설명하고 있습니다.

#include <iostream>
 
using namespace std;
 
class B
{
public:
        void test() { doTest(); }
 
        void foo() { bar(); }
 
protected:
        virtual void doTest() { cout << __PRETTY_FUNCTION__ << endl; }
 
private:
        virtual void bar() { cout << __PRETTY_FUNCTION__ << endl; }
};
 
class D1 : public B
{
private:
        virtual void doTest()
        {
                B::doTest();
 
                cout << __PRETTY_FUNCTION__ << endl;
        }
 
        virtual void bar() { cout << __PRETTY_FUNCTION__ << endl; }
};
 
int main()
{
        B b;
        D1 d1;
 
        b.test();
        d1.test();
 
        b.foo();
        d1.foo();
 
        return 0;
}

virtual void B::doTest()
virtual void B::doTest()
virtual void D1::doTest()
virtual void B::bar()
virtual void D1::bar()
jemiro의 이미지

필요한 어떤 클래스가 있고 그걸 다른 사람한테 만들어 달라고 요청해야 할 경우에
아래와 같이 Base를 만들어서 다른 사람한테 주면서, DoTest에 필요한 기능을 요청 합니다.
Derived 클래스를 만들어야 하는 개발자는 DoTest의 기능 뿐만 아니라 DoTest가 어떻게 사용되는지도
알면서 개발을 할수 있다는 장점이 있습니다.

class Base {
public:
  void Test() {
    for (int i = 0; i < 10; ++i) {
        DoTest(i);
    }
  }
private:
  virtual void DoTest(int n) = 0; 
}
 
class Derived: public Base {
private:
  void DoTest(int n) {
     cout << n << end;
  }
}

GunSmoke의 이미지

답변 매우 감사합니다. 예제를 통해서 더욱 많은 것을 알게되었습니다.

大逆戰

大逆戰

댓글 달기

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