생성자에서 가상함수를 호출하지 말자라는 주장에 대해

dltkddyd의 이미지

Effective C++ 99쪽에 객체 생성 및 소멸 과정 중에는 절대로 가상함수를 호출하지 말자라고 하였는데.

경우에 따라 생성시 가상함수가 호출되는 것이 프로그램머의 의도라면 그렇게 해도 되지 않을까요? 아래 간단한 예제 프로그램을 봐주세요.

#include <iostream>
using namespace std;
#include <cstdio>
 
 
class Parent {
public:
	int age;
	Parent():age(0) {
 
	}
	Parent(int _age):age(_age) {//1)번
		output();
	}
	virtual void output() {
		cout<<age<<endl;
	}
};
 
class Child:public Parent {
public:
	int toyNum;
	Child():toyNum(0),Parent() {
		printf("In Child, this is %p\n",this);
	}
	Child(int _toyNum, int _age):toyNum(_toyNum),Parent(_age) {
 
	}
	virtual void output() {
		cout<<toyNum<<endl;
	}
};
 
int main() {
	Child obj1(5,56);
 
	return 0;
}

1)번의 생성자에서 output이라는 가상함수를 호출하고 있습니다. 이 가상함수를 통해 값이 모두 설정된 객체를 출력하는 것이 프로그래머의 의도였다면 그렇게 해도 상관이 없을것 같은데요. 이런 생성자가 어디선가 의도하지 않은 문제를 유발시킬 문제가 있을까요?

dltkddyd의 이미지

가상함수라는 것이 상속자에서 재정의된 함수를 호출할 목적이기 때문에 그렇게 사용하지 말라는 것일까요? 상속받은 부모의 비가상멤버함수로 동일한 이름의 가상함수 호출시 자녀의 멤버함수가 호출되듯이 되지 않는 이상은 사용하지 말라라는 뜻인가요?

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

분명히 책에 그 이유도 적혀있을텐데...

http://www.artima.com/cppsource/nevercall.html

kukyakya의 이미지

"경우에 따라 생성시 가상함수가 호출되는 것이 프로그램머의 의도"라면 십중팔구 설계가 잘못된 것이거나 해당 프로그래머의 소양을 의심해 보아야 할 것입니다.

가상 함수의 목적이 뭔지 다시 생각해보시길 바랍니다.

dltkddyd의 이미지

질문글에 써있지 않습니까? 그 목적이 맞는 것인지 묻고 있는 것입니다.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

혹시나 해서..

Parent의 생성자에서 저렇게 output을 호출하면, Child의 output이 실행된다고 생각하고 계시는건 아니죠?

익명 사용자의 이미지

지금 올려주신 예제를 직접 실행해서 5가 아닌 56이 찍히는 걸 확인하고 질문하시는거죠?

dltkddyd의 이미지

그것 때문에 질문드린 것이 아닙니다. 56이 출력되는 것이 당연한 것이죠. 다만 생성 소멸시 가상함수 사용하지 말라는 책의 내용에 대해 질문드린 겁니다. 이것을 원칙으로 받아들이고 프로그램을 만들어야 하는 것인지가 궁금하기도 해서요. 예외를 두면 나중에 문제가 생기는 것은 아닌지 궁금하기도 하고요.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

dltkddyd의 이미지

암사직으로 this에 부모에 대응하는 객체주소가 전달되니 Parent의 output 함수가 호출되는 것으로 아는데요. this 출력해보니 그것이 부모의 주소던데요.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

네 그럼 생성자에서는 output이 가상함수임에도 불구하고 가상호출(child::output이 실행)이 일어나지 않는다는 걸 아셨겠네요.

프로그래머가 가상함수가 가상호출이 되지 않는 걸 몰랐다면, 읽고 계신 책을 잘 읽고 실수하지 말아야 할 것이고요,
프로그래머가 가상함수가 가상호출이 되지 않는 이상한 상황을 의도적으로 만들었다면, 자기 소양을 의심해봐야 한다는 것입니다.

kukyakya의 이미지

제가 하고자 하는 말이었습니다 감사합니다 ㅎㅎ

kukyakya의 이미지

지금 질문이 '생성자에서 호출할 함수가 하필 가상함수인데 실제론 비가상함수처럼 동작해도 상관없으면 사용해도 괜찮은 것 아닌가?' 아닙니까?

그리고 이 질문에 대한 대답은 EC++에서도 설명하듯이 '괜찮지 않다'이구요.

'가상 함수인데 비가상함수처럼 호출되는' 상황이 설계결함이라는 것이고 저 상황이라면 굳이 가상 함수를 적용할 필요도 없겠지요.

dltkddyd의 이미지

가상함수의 설계목적에 알맞게 사용해야 겠군요. 그렇다면 저런 식의 get 함수로 접근할 것이 아니라 비가상함수를 따로 만들어야 하는 것이군요.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

정확히 무슨 일이 일어나는지 알고 있고 상속 클래스와 부모 클래스간에 만족시켜야 할 스테이트 조건들에 대해 충분히 알고 있다면, 써도 됩니다. 이 경우 Parent 클래스의 작성자는 output이 non-virtual 멤버함수로 작동한다는 것을 알고 그것을 의도적으로 쓰면 괜찮습니다, 해당 기능을 하는 non-virtual 멤버 함수를 명시적으로 만드는 수고를 굳이 아껴야겠다면 말이지요.

c++의 작동 방식을 정밀하게 다 고려해서 하는 거라면, 그런 식으로 할 수도 있습니다. 안될게 뭐있나요. 다만 그런 식으로 만들어진 코드들은 보통 못쓸 물건이 되더군요. 작성하는 순간에는 제 영리함이 뿌듯하겠지만 말이지요.

dltkddyd의 이미지

this가 자기 자신의 주소를 받을 때와 this가 자식클래스로 선언된 객체 주소를 전달받을 때의 경우가 틀린가요? this가 후자를 받을 때는 virtual로 작동하고 자기 자신의 주소를 받을 때는 non-virtual 멤버함수로 작동한다는 말씀이신지. 아직 그런 클래스로 프로그램 만들어 본적이 없거든요.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

저기 위에, 읽고 계신 책의 내용을 링크로 걸어주신 분이 있네요. 거기 3, 4, 5 단락을 읽어보세요.

kukyakya의 이미지

parent 멤버 함수 내에서의 this와 child 멤버 함수 내에서의 this는 값이 다를 수 있습니다. 그리고 this의 값이 다르다는 사실이 가상함수와는 전혀 상관이 없습니다.

익명 사용자의 이미지

이건 제가 잘 몰라서.. 어떤 경우에-혹은 어떤 플랫폼/구현체에서- 다를 수 있나요?

kukyakya의 이미지

익명 사용자의 이미지

아하 그렇군요. 감사합니다.

다중상속된 경우에도 b의 멤버함수가 b의 인스턴스와 c의 인스턴스 모두에서 동일한 오프셋 주소로 bb에 접근하게 하려면 그렇게 되어야겠네요.

dltkddyd의 이미지

그 get 함수가 non-virtual 멤버함수라는 말씀은 아니시죠? non-virtual 멤버함수인 것인양 작동한다는 말씀이신 것 같아서요. 그리고 아래의 코드가 질문드린 그 경우가 아닌가 하는데요. 생성시 부모부터 아래 방향으로 순차로 객체가 생성되는데, get() 함수가 호출되는 중에는 아직 그 하부가 생성되지 않은 상태이기 때문에 virtual int get 함수가 하나만 선언되어 있는 경우가 되는 것인가요? Parent 생성자가 호출되는 시점에요.

#include <iostream>
using namespace std;
 
class ReTest {
private:
	int pole;
public:
	ReTest():pole(0) {}
	ReTest(int _pole):pole(_pole) {}
	virtual int get() {return pole;}
};
 
int main() {
	ReTest obj1(20);
	cout<<obj1.get()<<endl;
	return 0;
}

그러면 그 순간이(Parent에서 get()이 호출되는 것) 위의 코드처럼 가상함수가 하나만 존재할 때 obj1.get()이 호출되는 것고 유사하겠네요. 바로 위의 경우도 non-virtual 멤버함수로 작동하겠군요. 비록 위의 코드에서는 쓸모없는 가상함수이기는 합니다만.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

이거 일부러 낚시하는거죠?

Effective C++ 3판 챕터 2 항목 9번이 바로 "객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자"에 대한 항목이고
당연히 해당 항목에서 왜 그래선 안되는지 친절하게 설명해주고 있습니다.

해당 항목을 '발췌'한 다음 링크를 보세요.
http://www.artima.com/cppsource/nevercall.html

Things to Remember : Don't call virtual functions during construction or destruction, BECAUSE SUCH CALLS WILL NEVER GO TO A MORE DERIVED CLASS THAN THAT OF THE CURRENTLY EXECUTING CONSTRUCTOR OR DESTRUCTOR.

한국어판 가지고 읽었으면 당연히 대문자로 쓴 부분이 친절하게 한국어로 잘 적혀 있을 텐데,
그런데 책은 안읽고 와서 책에 설명해 놓은 부분을 그대로 질문한뒤에,
책에 적혀있는 거랑 똑같은 설명을 듣고서는 만족하고 있네요...

이거 대체 무슨 상황이죠?

dltkddyd의 이미지

그게 질문하고 답변 듣다 보니 알게 된 겁니다. 오해의 소지가 있는 부분을 정리해 놓으신 거더군요. 전 처음에 생성자 호출시 암시적으로 전달받는 매개변수 this가 자신의 타입에 맞는 즉, 상위 클래스의 생성자가 호출될 때에는 딱 그에 해당하는 객체가 전달된는 것으로 알고 있었습니다. 여기서부터 책의 설명 내용과 다른 책에서 읽은 내용이 오버랩 되면서 조금 헷갈렸던 겁니다. 여러분들이 말씀하신 부분을 잘 들어보니 이니셜라이져로 생성자 호출시에는 그 하부의 객체가 전달되게 되는 것이죠. 그렇게 알고 있었다면 그렇게 호출하면 안 된다는 사실을 알았겠지만요. 그런 것들에 대해 오해하고 있어서 생기는 문제였습니다. 영문 책을 읽어서 개념이 제대로 잡힌 것이 아니라요. 책에 있는 상황이 다른 읽은 책과 짬봉이 되면서 헷갈렸던 것이죠.
그런 상황이 이런 상황이었습니다.
이런 혼돈을 정리할 수 있도록 해주신 답변에 감사드립니다. 왜 오버라이딩을 가상함수를 써서 하라고 하셨는지도 알것 같고요. 예전에 vector를 상속받아서 svector 클래스 만들려 할 때도 vector에는 비가상함수만 정의돼 있으니 상속받아서 쓰는 것을 지양해라라는 당부도 무슨 뜻인지 알 것 같습니다. 재정의해서 사용할 수 있는 함수가 하나도 없는데 재정의를 해놓으니 터무니 없게 보신 것 같습니다. 제 무지에 대해 죄송하게 생각하고, 무례하고 굴었던 점이 다소 있었다면 용소해주시길 바랍니다.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

그러면요. 책 내용 읽었지만 이해가 안된다고 물어보세요. 뭔 프로그래머 의도가 어떻느니 어떻게 동작하느니 추측으로 헛소리 하지 말고요. 어떻게 동작하는지 왜 그렇게 동작하는지 책에 고대로 설명되어 있는데 지금 이 지랄한 거 아니예요. 질문하기 전에 자기가 생각한 것을 코딩해보고 돌려보고 그 결과가 예상한 것과 같은지 정확히 꼼꼼하게 확인해보고 나서 그게 예측에서 벗어날 때, 그 때 물어보세요. 제대로 컴파일되는 소스와 그 출력 결과와 함께요.

익명 사용자의 이미지

'이거 대체 무슨 코메디같은 상황인지...'라는 리플 쓴사람인데
위에 오후 10:29에 쓰여진 리플은 내가 적은게 아닙니다.

그리고 예전에 vector에 대해서 답변한 것도 내가 아닙니다.

난 다른 사람 똥은 책임지고 싶지 않습니다.

dltkddyd의 이미지

책이 전부를 설명하고 있지는 않더군요. 저자는 자신이 알고 있는 사실을 독자도 알거라고 짐작하고 썼기 때문에 좀 설명을 부족하게 해놓은 겁니다. 좀 설명이 모자르죠. 저자도 서두에 그런 말을 했더군요. 이 책은 바이블이 아니라고요. 그리고 어느 정도 C++을 알고 있는 독자를 대상으로 저술하신 거고요. 저 가상함수에 대한 질문을 한 것도 그런 뜻에서 한 거고. 좀 이상한 말을 하지 말고 너부터 똑바로 하세요. 말했지만 책 읽고 알게 된 것이 아니라 여러 가지로 생각하다 보니 알게 됐다고 말했잖아 멍청아.
이 책은 몇 가지 주의사항들에 대한 권고로 받아들이면 족하겠어요. 기본서로 읽을 책은 아닌 것으로 보입니다. 알아두면 나쁘지는 않겠지만. 그리고 이 책 군데 군데 코드들도 몇 개 빠트리더군요.

141 페이지
class Month에서

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

기본서부터 읽으란 얘기도 이미 진작에 누군가 했었죠 아마..

jick의 이미지

대부분의 경우에 base class와 상속받은 class의 this 주소는 같습니다. (메모리에 어떻게 저장되는지 그려보면 쉽게 이해가 갑니다. 일반적으로 base class에 해당하는 필드가 먼저 저장되고 그 뒤에 derived class에서 추가한 필드가 따라오겠죠. 반드시 그래야 한다고 표준으로 정해져 있는지는 잘 모르겠습니다만...) 이 주소가 달라지는 경우는 위에 kukyakya 님이 잘 적어주셨구요.

그러니까 (너무나 당연한 얘긴데) this 포인터의 주소값을 찍어본다고 이게 "base의 this인가 상속받은 클래스의 this인가"를 알 수는 없습니다.

C의 대원칙이 "메모리 주소만 가지고는 그 위치에 뭐가 있는지 알 수 없으니까, 그 위치에 뭐가 들어있는지는 니가 (프로그래머가) 알고 있어야 한다"입니다. C++도 C에서 파생된 언어라서 마찬가지입니다.

dltkddyd의 이미지

그 get 함수가 non-virtual 멤버함수라는 말씀은 아니시죠? non-virtual 멤버함수인 것인양 작동한다는 말씀이신 것 같아서요. 그리고 아래의 코드가 질문드린 그 경우가 아닌가 하는데요. 생성시 부모부터 아래 방향으로 순차로 객체가 생성되는데, get() 함수가 호출되는 중에는 아직 그 하부가 생성되지 않은 상태이기 때문에 virtual int get 함수가 하나만 선언되어 있는 경우가 되는 것인가요? Parent 생성자가 호출되는 시점에요.

#include <iostream>
using namespace std;
 
class ReTest {
private:
	int pole;
public:
	ReTest():pole(0) {}
	ReTest(int _pole):pole(_pole) {}
	virtual int get() {return pole;}
};
 
int main() {
	ReTest obj1(20);
	cout<<obj1.get()<<endl;
	return 0;
}

그러면 그 순간이(Parent에서 get()이 호출되는 것) 위의 코드처럼 가상함수가 하나만 존재할 때 obj1.get()이 호출되는 것고 유사하겠네요. 바로 위의 경우도 non-virtual 멤버함수로 작동하겠군요. 비록 위의 코드에서는 쓸모없는 가상함수이기는 합니다만.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

아니요. 님이 새로 올린 이 예제는 그냥 가상함수를 가진 하나의 클래스일 뿐이고, 그 외 원래 주제와 아무런 관련성이 없습니다.

님은 개념을 부정확하게 사용하면서 그것들을 마구 뒤섞고 있습니다. 님이 자의적으로 만들어낸 혼돈 속으로 다른 사람들을 끌어들이는 방식은 좋은 질문 태도가 아닙니다.

님이 이해해야 할 것은 Things to Remember 이상도 이하도 아니고, 그보다 깊은 것은 C++ specification과 implementation detail에 가깝습니다. C++ 로 개발하는 대부분의 개발자들에겐 별로 필요가 없는 것들이고, 그래서 그 책에도 그 정도까지만 기술한 것입니다.

사실 님의 "현재" 학습 수준에서는 그 절의 제목만 기억해도 충분하리라 보지만, 정 더 알고 싶으면 직접 C++ 스펙 문서를 보시고, 거기에 사용된 정확한 용어와 개념에 바탕해서 질문하시기 바랍니다.

simminjo의 이미지

댓글에 좋아요를 누를수가 없다는 것이 아쉽군요.....

---------------------------------------------------------------
Opensource에 기여하는 것이 꿈입니다.
내가 만든 코드를 모두가 사용할 때 까지~

댓글 달기

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