[완료] 클래스 멤버 함수 포인터에 관한 질문드립니다.

parkon의 이미지

요즘 제가 염치 없이 여기에 질문글을 많이 올리는 군요...,

하고 싶은 건 추상 클래스에서 구상 클래스의 멤버 함수 포인터를 인자로 받아 오는 건데요,

대충 코드를 써 보면

class Abstract {
 
   void Draw(double (*fcn)(double)) {
      // 이 함수 fcn에 해당하는 그림 그리는 루틴
   }
}
 
class Concrete : public Abstract {
   double f1(double x);
   double f2(double x);
   //...
   void Draw1() { Draw(&Concrete::f1; }
   void Draw2() { Draw(&Concrete::f2; }
   //...
}

이런 식을 원하는데 잘 안되는군요.

실제 문제는 위 보다 많이 복잡하고 제가 또 좌충우돌 하다보니
정확히는 말씀드리기 어렵지만
위처럼 했더니만
뭐 non-static member 함수는 주소를 받을 수 없다는 (?) 에러가 나오고

위의 Draw1을

typedef double (*fun)(double);
void Draw1() { Draw(reinterpret_cast<fun>(&Concrete::f1); }

이렇게 바꾸니까
double (Concrete::*)(double)을 double (*)(double)로 바꾸려 한다는 warning이 뜨는군요.

제 고민에 조언 주시면 감사하겠습니다.

shint의 이미지

static 으로 주니까 되기는해요.

class C
{
public:
	void method( double (* call) (double a) )
	{
		call(10.1);
	}
};
 
class B : public C
{
public:
	B() {}
	~B() {}
 
	static double fn1(double n){ TRACE("%f\n", n); return 0; };
	static double fn2(double n){ TRACE("%f\n", n); return 0; };
 
public:
	void method1()
	{
		method(fn1);
	}
	void method2()
	{
		method(fn2);
	}
};
 
 
void main()
{
	B b;
	b.method1();
	b.method2();
}

요기 참조 : http://cafe.naver.com/develx.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=144&

----------------------------------------------------------------------------
젊음'은 모든것을 가능하게 만든다.

매일 1억명이 사용하는 프로그램을 함께 만들어보고 싶습니다.
정규 근로 시간을 지키는. 야근 없는 회사와 거래합니다.

각 분야별. 좋은 책'이나 사이트' 블로그' 링크 소개 받습니다. shintx@naver.com

shint의 이미지

되기는 하지만. 설명은 못하겠네요. ㅇ_ㅇ;;;

void * OFFSET(...)
{
	_asm mov eax,dword ptr [ebp+8];
}
 
class C
{
public:
	double (C::* callx) (double a);
 
	void method( double (C::* call) (double a) )
	{
		*(DWORD *)&callx = (DWORD)OFFSET(call);
		((this)->*call)(10.1);
		((this)->*callx)(10.2);
	}
};
 
class B : public C
{
public:
	B() {}
	~B() {}
 
	double (C::* callx) (double a);
 
	double fn1(double n){ TRACE("%f\n", n); return 0; };
	double fn2(double n){ TRACE("%f\n", n); return 0; };
 
public:
	void method1()
	{
		*(DWORD *)&callx = (DWORD)OFFSET(&B::fn1);
		method(callx);
	}
	void method2()
	{
		*(DWORD *)&callx = (DWORD)OFFSET(&B::fn2);
		method(callx);
	}
};
 
 
void main()
{
	B b;
	b.method1();
	b.method2();
}

출력결과
10.100000
10.200000
10.100000
10.200000

----------------------------------------------------------------------------
젊음'은 모든것을 가능하게 만든다.

매일 1억명이 사용하는 프로그램을 함께 만들어보고 싶습니다.
정규 근로 시간을 지키는. 야근 없는 회사와 거래합니다.

각 분야별. 좋은 책'이나 사이트' 블로그' 링크 소개 받습니다. shintx@naver.com

parkon의 이미지

헉 고맙습니다.
일단 static으로 하는건 제 경우 멤버 변수들을 마구 참조해야 하는 관계로 저한테는 안맞을것 같구요,
제 지식이 짧아 아직은 잘 모르겠지만
올리신 두번째 코드 보니까 일단 서광이 확 비치네요.
근데 저 OFFSET구문,플랫폼에 의존하겠지요 ?
이 의존성만 없앨수 있다면 금상첨화일텐데요,,,

isson16127의 이미지

32비트 시스템에서 일반 함수 와 클래스의 정적 멤버 함수는 4바이트 포인터 값하나로 표현가능합니다.

하지만 클래스의 비정적 멤버함수의 경우 "오브젝트주소 + 비정적 멤버 함수 주소" 형태가 되어야 정상적인 포인터 값으로서 의미가 있습니다. 비정적 멤버 함수내부에서 클래스 멤버 변수를 참조하는 코드는 항숭 오브젝트의 주소를 알고 있어야 하기때문입니다.

따라서 다음과 같은 코드는

obj.funcA();

컴파일러가 funcA 멤버함수를 호출하기 위해 obj의 주소와 funcA 멤버함수의 주소 모두를 알고 있어야 호출가능합니다.

MyClass::funcA();

와 같은 함수호출코드는 컴파일러가 단순히 MyClass 네임스페이스의 funcA 함수 주소만 알고있어도 호출가능합니다.

따라서 ANSI C++ 에서는 비정적 멤버 함수의 주소를 포인터 변수에 저장할수 없습니다. 32비트 시스템에서 오브젝트 주소 + 함수 주소의 8바이트값을 4바이트 포인터 변수에 넣을수가 없기때문입니다.

볼랜드 계열 (지금 이름은 엠바카데로) C++ 컴파일러는 이러한 경우를 대비하여 멤버함수 포인터 타입을 제공합니다. 지금 잘 기억이 안나는데 32비트 시스템에서 8바이트 크기를 가집니다. 그리고 당연히 해당 포인터로 멤버함수를 호출할 수 있습니다. 키워드가 지금 기억이 안나는데, 찾아보시면 금방 아실수 있을겁니다.

따라서 원하시는 작업은 볼랜드사의 C++ builder 에서는 쉽게 가능합니다. MS 계열의 Visual C++ 에서는 일반적으로 할수 없고, 인라인 어셈블리 코드를 직접 작성하셔야 합니다. Visual C++ 의 ATL 소스를 parkon 님이 원하시는 작업을 하기 위해 썽킹이라는 기술을 사용합니다. 인라인어셈블리를 사용한 트릭인데요. 코드 프로젝트의 atl 기초강좌(몇개 안됩니다)를 찾아보시면 잘 기술되어 있는 강좌를 찾아 보실수 있습니다.

parkon의 이미지

isson님 답변 감사합니다.
설명 듣고 보니 제가 하려고 하는게 그렇게 쉽고 단순한 문제는 아닌듯 하군요.
(전 처음엔 아주 흔한 케이스이고, 그래서 아주 쉬운 해법이 있는데 무식한 저만 모르는 문제 정도로 여겼습니다.)
제 경우는 구현을 포기하는 한이 있더라도 플랫폼 의존성은 용서가 안되는 관계로
일단은 그대로 적용하기는 어렵겠네요.
그래도 덕분에 왜 non-static 멤버 함수의 포인터가 문제가 되는지는
이제 잘 이해했습니다.
어떻게 해야 할지 고민 좀 더해 봐야 겠습니다.

jick의 이미지

찾아보니 이런 얘기가 있네요.

http://stackoverflow.com/questions/4272909/is-it-safe-to-upcast-a-method-pointer-and-use-it-with-base-class-pointer/

struct B
{
    typedef void (B::*MethodPtr)();
};
 
struct D: public B
{
    void foo() { cout<<"foo"<<endl; }
};
 
int main(int argc, char* argv[])
{
    D d;
    B* pb = &d;
 
    //is the following ok, or undefined behavior?
    B::MethodPtr mp = static_cast<B::MethodPtr>(&D::foo);
    (pb->*mp)();
}
parkon의 이미지

jick님, 답변 고맙습니다.
아주 솔깃한 해법이군요. 잘 될것같은 예감이... ^^
있다가 위 방법대로 해보고 제 결과를 올리도록 하겠습니다.

windowsprogrammer의 이미지

using namespace std;
 
class Abstract
{
protected:
	void Draw(std::function<double(double)> foo) {
		// 이 함수 fcn에 해당하는 그림 그리는 루틴
		foo(0.1f);
	}
};
 
class Concrete : public Abstract {
public:
	double f1(double x)
	{
		printf("%f", x);
		return 0.1;
	}
	//...
	void Draw1()
	{
		std::mem_fun1_t<double, Concrete, double> a(&Concrete::f1);
		std::binder1st<mem_fun1_t<double, Concrete, double> > b(a, this);
 
		Draw(b);
	}
	//...
};
 
int _tmain(int argc, _TCHAR* argv[])
{
	Concrete c;
	c.Draw1();
	return 0;
}

멤버 함수를 다른 곳에 대입하려면 먼저 멤버 함수를 this를 포함해 인자가 1개 더 추가된 일반 함수로 바꾸어주어야 합니다.
위 코드에서 mem_fun_1_t가 그런 일을 합니다.
그 후 binder1st를 사용해서 this를 바인드하여 다시 인자가 1개짜리인 함수로 만들었고 이를 std::function을 통해서 넘겨주었습니다.

그런데 위 클래스의 설계는 완전히 잘못되었습니다. 가상함수나 다형성을 조금 더 공부하시고 설계에 대해서 다시 한번 고민해보시는 것이 좋겠습니다.
힌트를 드리면 추상 클래스가 콘크리트의 구현을 알아야만 한다는 것이(함수 포인터를 대입해주기 때문) 이상한 부분입니다.

parkon의 이미지

위 jick님 방법데로 (제 상황에 맞게 수정해서) 해 보니 컴파일도 런도 잘 됩니다.
많은 도움 되었습니다...^^

Windowsprogrammer님 조언 감사드립니다.
위 내용뿐만 아니라 프로젝트 전체적으로 클래스 설계가 얼키고 설키고 엉망인건 몸으로 절감하고 있지만
능력과 시간 부족으로... ^^
가르쳐 주신 방식과 조언은 시간을 두고 천천히 음미해봐야 겠습니다.

두분과 제 글에 관심을 보여주신 모든 분께 다시 한번 감사드립니다... 꾸뻑.

댓글 달기

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