c++ 함수 매개변수 다형성 처리에 대한 질문입니다.

kirby의 이미지

c++에 익숙하지 않아서 질문의 의도를 제대로 표현했는지 모르겠습니다.

코드로 나타내면 아래와 같습니다.

#include "stdafx.h"
 
#include <iostream>
#include <string>
 
using namespace std;
 
class C1 {
public:
    virtual void call(const C1& c) { cout<<"C1 calls C1"<<endl; } 
    virtual void call(C1* p) { cout<<"C1 calls C1"<<endl; }
};
 
class C2 : public C1 {
public:
    virtual void call(const C1& c) { cout<<"C2 calls C1"<<endl; }
    virtual void call(const C2& c) { cout<<"C2 calls C2"<<endl; }
    virtual void call(C1* p) { cout<<"C2 calls C1"<<endl; }
    virtual void call(C2* p) { cout<<"C2 calls C2"<<endl; }
};
 
 
int _tmain(int argc, _TCHAR* argv[])
{
    C1* p1 = new C1;
    C1* p2 = new C2;
 
    p2->call(*p1);     // 1번
    p2->call(*p2);     // 2번
 
    p2->call(p2);      // 3번
 
    return 0;
}

위의 예에서, 제가 원하는 것은 2,3번에서
"C2 calls C2"가 호출되는 것인데, vs2008에서는 그렇게 되지 않네요.

혹시 제가 원하는 대로 호출할 수 있는 방법이 있을지 궁금합니다.

참고로 자바에서는 제가 원하는 대로 출력이 되었던 것 같습니다.

klyx의 이미지

자바는 잘 모르겠습니다.

C++의 다이다믹 바인딩은 런타임에 결정되지만, 함수오버로딩은 런타임이 아니라 컴파일 타입에 넘겨주는 변수의 타입으로 결정됩니다.
오버로딩이 별다른게 아니고, 그냥 같은 이름으로 된 함수를 컴파일러가 타입으로 알아서 판단하여 적당한 함수를 호출해주는 것입니다.
예를 들어
void call(C1*);
void call(C2*);
두개의 오버로딩된 함수가 있다면, 컴파일러는
void call_C1(C1*);
void call_C2(C2*);
와 같이 별도의 함수이름을 만들고, 이 함수가 호출되는 부분에서 타입을 유추하여 적당한 함수를 호출합니다.
당연히 다이나믹바인딩과는 전혀 무관하고, 따라서 질문하신내용은 다형성과는 전혀 관계없는 문제입니다.
예를 들어 C1과 C2를 모두 상속한 클래스 객체를 call의 인자로 넘겨준다면 컴파일 단계에서 오버로딩된 함수를 정할수 없다고 에러가 발생하여 컴파일 자체가 불가능할 것입니다.

kirby의 이미지

막연하게나마 그렇지 않을까 생각을 했습니다.다만 자바에서 그러한 기능을 잘 썼기 때문에 c++에서도 자바처럼 지원해주지 않을까하고 기대했었습니다. 객체 관점에서 보면 자바처럼 해 주는 것이 더 맞을 것 같습니다.
그리고 vs2008에서는 컴파일 에러가 뜨지 않았습니다. 2번의 경우 그냥
C2 calls C1
이 나왔습니다. 사실 잘 이해가 되지 않는 결과입니다.

klyx의 이미지

글을 읽으신건지 모르겠네요....

한 말씀만 드리자면...

>> 다만 자바에서 그러한 기능을 잘 썼기 때문에 c++에서도 자바처럼 지원해주지 않을까하고 기대했었습니다. 객체 관점에서 보면 자바처럼 해 주는 것이 더 맞을 것 같습니다.

그럼 자바를 쓰세요.

kirby의 이미지

오해를 불러 일으킨 것이 아닌가 하는 생각이 들어 죄송합니다.
그냥 객체 관점에서 매개변수도 동적으로 처리되는 것이 좀더 바람직하지 않은가 하는 생각에 드린 말씀입니다. c++도 객체지향 언어니까요.
지금은 아니더라도 나중에라도 바뀌었으면 좋겠다는 희망도 있고요. 바뀔 수 있는지 여부는 모르겠습니다만.

혹시 제가 잘못 생각하고 있는 것이라면 지적하여 주시면 감사하겠습니다. 예를 들면, 매개 변수는 정적으로 처리되는 것이 더 바람직하다던가, c++는 본질적으로 그렇게 못한다던가... 등등..

klyx의 이미지

C++은 언어레벨에서 garbage collector를 지원하지 않습니다.
메모리 할당과 해제의 흐름이 명확한 경우에도 garbage collector가 강제되는 것은 큰 손실입니다.
필요하다면 라이브러리 레벨에서 구현된 스마트 포인터등을 이용하면 됩니다.

C++은 virtual이란 키워드로 가상함수를 선언합니다.
가상함수일 필요가 없는 함수를 무조건 가상함수로 강제하는 것은 큰 손실입니다.
필요한 함수만 가상함수로 선언하면 됩니다.

오버로딩에 대해서도 마찬가지입니다.
상속되지 않은 클래스의 객체를 인자로 받는 오버로딩된 함수들을 런타임에 매번 타입을 체크하여 결정하는 것은 큰 손실입니다.
필요하다면 dynamic_cast를 이용하거나 Meta-Object 를 직접 구현하면됩니다.

Java와 C++을 비교해서 C++에서 '안된다'는 것은 언어레벨에서 안되는 것이지 근본적으로 불가능하다는 것은 아닙니다.
(일부 예외는 있습니다. 예를 들어 Java의 final키워드는 C++에도 하나쯤 있으면 좋겠습니다.)
사용자가 직접 구현 가능한 기능들을 언어에 기본스펙으로 내장해버리는 것은, 그 기능이 필요없는 사람에게는 쓸데없는 오버해드일 뿐입니다.
구현 가능한 기능은 라이브러리레벨에서 구현하여 필요한 사람만 쓰면됩니다.
컴파일타임에 결정 가능한 것을 런타임으로 가져가는 것은 성능면에서 결코 좋을 수 없습니다.
컴파일타임에서 메타프로그래밍이 가능하게 하는 템플릿 메타프로그래밍은 마법이라고도 불립니다.
전 이러한 부분에서 C++이 Java와 차별화가 된다고 생각합니다.

kirby님은 왜 C++을 쓰려고 하시는지요?
앞에서도 적었듯이, 편리함과 성능은 trade-off관계에 있습니다.
이미 자바에 대해서 숙지하고 계시고 이런 성능문제가 이슈가 되지 않는다면 그냥 자바를 쓰시면될 것입니다.
만약 성능이 문제가 되어서 C++을 쓸려고 하는 것이라면, 당연히 자바에서 편하다고 생각하던 기능들을 포기해야 하는 것들도 있을 것입니다.
그냥 C++을 써보고 싶어서 라면 앞에서 적었듯이 C++은 불필요한 성능이슈는 언어에 포함시키지 않아왔기 때문에,
성능과 관련된 부분은 그걸 감안하고 써야하며, 필요한 경우는 직접 구현하거나 이미 구현된 라이브러리를 이용해야하시면 됩니다.

richjaff의 이미지

C++에서는 객체가 두가지로 존재합니다.

자바에서는 객체를 담아내는 변수로 참조변수 한가지 밖에 없지만,
C++에는 객체를 변수처럼 보관하는 "객체" 타입과, 자바처럼 메모리 참조값만 가지는 "포인터 타입"이 존재합니다.

C1* p1 = new C1;
C1* p2 = new C2;

이렇게 선언했을 경우, 물론 자바처럼
p2로 C2클래스 맴버함수를 호출할 수 있습니다만,
말그대로 C1*로 선언하였기 때문에,
*p2로 사용을 할 경우, C1의 객체타입이 되는 것입니다.

C++에서는 C1에서 C2로 캐스팅을 함에 있어서 포인터 간에 캐스팅은 가능하지만,
객체형태 자체의 캐스팅은 불가합니다.

따라서 이렇게 해보시면 잘 될것입니다.

============================================================

C1* p1 = new C1;
C1* p2 = new C2;

C2* test = (C2*)p2;

C2 p3;
p3.call((C2)*test);

============ 이렇게 바꿔도 가능합니다. =========================

(*(C2*)p2).call(*p1); // 1번
(*(C2*)p2).call(*((C2*)p2)); // 2번

(*(C2*)p2).call((C2*)p2); // 3번

kirby의 이미지

알려 주신 코드는 원하는 출력이 나옵니다. 다만 그렇게 하면 굳이 상속을 이용할 필요가 없을 것 같아서 원래 계획했던 방법대로 쓰기는 어려울 것 같습니다.

그리고 하나 아직 이해가 안되는 부분이,
p2->(test)를 호출하면 "C2 calls C1"이 나오는데, 이 부분이 잘 이해가 되지 않습니다. 차라리 컴파일 오류가 뜨면 그려러니 받아들겠습니다만... 그냥 생각하기에는 다형성에 의해서 C2 calls C2가 나올 것으로 생각됩니다만...

괜찮으시다면 설명을 부탁 드려도 될른지요?

richjaff의 이미지

함수가 호출되는 순서때문에 그렇습니다.
우선 위의 코드에서 오버라이딩된 함수의 실행순서를 살펴보면 다음과 같습니다.

두 코드의 차이점
1.p2->call(test);
2.(*(C2*)p2).call(test);

1번의 실행순서
- p2는 C1 클래스 타입에서 다음의 정의내용을 참조합니다.
- virtual void call(C1* p);
- 오버라이딩 키워드가 있는 것을 확인하고, 다음 C2클래스에서 위의 같은 타입에 대해 오버라이딩되어진 함수를 호출합니다.
- 따라서, 최종적으로는 C2 클래스 내에 있는 다음 함수가 호출되어지는 것입니다.
virtual void call(C1* p);

결론: C1에서 먼저 정의를 참조하고, C2에서 동일한 타입의 오버라이딩된 매개변수가 (C1*)타입인 함수가 호출되어지는 것입니다.

테스트 해보면 아시겠지만,
C1 클래스에서 virtual void call(C1* p); 함수에 주석처리 하면 컴파일 에러가 납니다.

매개변수 자체의 다형성은 제대로 인식이 되어지지만, 함수의 오버라이딩의 과정이 위와 같이이루어지기 때문입니다.
이렇게 한번 더 테스트 해보시기 바랍니다.

=======================================================

class C2;

class C1 {
public:
virtual void call(C2* p) { } // 추가해주시면 됩니다.
};

이렇게 하면 원하는 결과가 나올 겁니다.

kirby의 이미지

깔끔하게 이해가 되었습니다.
p2->call(test)에서
전 p2의 클래스 타입 C2가 먼저 해석되고, C2의 call이 호출될 것으로 생각했었는데, 아니었네요. 덕분에 많이 배웠습니다.
감사합니다.

winner의 이미지

테스트를 해봐야 알겠지만 발제자는 지금 multi method(다중 dispatch)를 이야기하는 것 같은데
Java도 안 될텐데요.

winner의 이미지

어떻게 하셨었죠?

purluno의 이미지

public class Test {
	public static class C1 {
		public void call(C1 o1) {
			System.out.println("C1 calls C1");
		}
	}
 
	public static class C2 extends C1 {
		public void call(C1 o1) {
			System.out.println("C2 calls C1");
		}
 
		public void call(C2 o2) {
			System.out.println("C2 calls C2");
		}
	}
 
	public static void main(String[] args) {
		C1 o1 = new C1();
		C2 o2 = new C2();
		o2.call(o1);
		o2.call(o2);
		C1 o3 = o2;
		o3.call(o2);
	}
}

실행결과

C2 calls C1
C2 calls C2
C2 calls C1

Java라 하더라도 오버로딩한 메소드 중 어떤 메소드를 호출할 지까지 run-time에 결정하진 않습니다. 즉, 컴파일 단계에서 결정합니다. 그래서 위 방법은 일관성 문제가 발생하기 때문에 쓰지 않는 것이 좋습니다. (명확히 하면 call(C2 ...)는 상위클래스로부터 재정의한 메소드가 아닙니다.) 확실하게 run-time으로 인자 객체의 서브클래스 소속 여부를 확인하려면 reflection을 이용한 다음 방법을 사용해야 합니다.

public class Test {
	public static class C1 {
		public void call(C1 o1) {
			System.out.println("C1 calls C1");
		}
	}
 
	public static class C2 extends C1 {
		public void call(C1 o1) {
			if (o1 instanceof C2) {
				System.out.println("C2 calls C2");
			} else {
				System.out.println("C2 calls C1");
			}
		}
	}
 
	public static void main(String[] args) {
		C1 o1 = new C1();
		C2 o2 = new C2();
		o2.call(o1);
		o2.call(o2);
		C1 o3 = o2;
		o3.call(o2);
	}
}

실행결과

C2 calls C1
C2 calls C2
C2 calls C2

klyx의 이미지

낚인거군요...

댓글 달기

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