문제

C++에서 멤버 함수 포인터를 사용하여 파생된(또는 기본) 클래스 멤버를 가리킬 수 있습니까?

편집하다:아마도 예가 도움이 될 것입니다.세 개의 클래스로 구성된 계층 구조가 있다고 가정해 보겠습니다. X, Y, Z 상속 순으로.Y 따라서 기본 클래스가 있습니다 X 그리고 파생 클래스 Z.

이제 멤버 함수 포인터를 정의할 수 있습니다. p 반을 위해서 Y.이는 다음과 같이 작성됩니다.

void (Y::*p)();

(단순화를 위해 서명이 있는 함수에만 관심이 있다고 가정하겠습니다. void f() )

이 포인터 p 이제 클래스의 멤버 함수를 가리키는 데 사용할 수 있습니다. Y.

이 질문(실제로는 두 가지 질문)은 다음과 같습니다.

  1. 할 수 있다 p 파생 클래스의 함수를 가리키는 데 사용됩니다. Z?
  2. 할 수 있다 p 기본 클래스의 함수를 가리키는 데 사용됩니다. X?
도움이 되었습니까?

해결책

C++03 표준, §4.11 2 멤버 변환에 대한 포인터:

"유형의 B 멤버에 대한 포인터" 유형의 rvalue 이력서 B가 클래스 유형인 T'는 유형의 D 멤버에 대한 포인터 유형의 rvalue로 변환될 수 있습니다. 이력서 T”, 여기서 D는 B의 파생 클래스(10절)입니다.B가 D의 액세스 불가능(11절), 모호(10.2) 또는 가상(10.1) 기본 클래스인 경우 이 변환이 필요한 프로그램은 잘못된 형식입니다.변환 결과는 변환이 발생하기 전의 멤버에 대한 포인터와 동일한 멤버를 참조하지만, 기본 클래스 멤버가 파생 클래스의 멤버인 것처럼 참조합니다.결과는 B의 D 인스턴스에 있는 멤버를 나타냅니다.결과에는 "유형의 D 멤버에 대한 포인터" 유형이 있으므로 이력서 T'는 D 객체로 역참조될 수 있습니다.결과는 B의 멤버에 대한 포인터가 D의 B 하위 개체로 역참조된 것과 같습니다.널 멤버 포인터 값은 대상 유형의 널 멤버 포인터 값으로 변환됩니다. 52)

52)멤버에 대한 포인터 변환 규칙(베이스의 멤버에 대한 포인터에서 파생된 멤버에 대한 포인터)은 개체에 대한 포인터에 대한 규칙(포인터에서 파생된 포인터에서 베이스에 대한 포인터)(4.10, 절 10)에 비해 반전된 것으로 나타납니다.이 반전은 형식 안전성을 보장하는 데 필요합니다.멤버에 대한 포인터는 객체에 대한 포인터나 함수에 대한 포인터가 아니며 이러한 포인터 변환 규칙은 멤버에 대한 포인터에 적용되지 않습니다.특히 멤버에 대한 포인터는 void*로 변환될 수 없습니다.

즉, 멤버가 모호하지 않은 한 액세스 가능하고 가상이 아닌 기본 클래스의 멤버에 대한 포인터를 파생 클래스의 멤버에 대한 포인터로 변환할 수 있습니다.

class A {
public: 
    void foo();
};
class B : public A {};
class C {
public:
    void bar();
};
class D {
public:
    void baz();
};
class E : public A, public B, private C, public virtual D {
public: 
    typedef void (E::*member)();
};
class F:public E {
public:
    void bam();
};
...
int main() {
   E::member mbr;
   mbr = &A::foo; // invalid: ambiguous; E's A or B's A?
   mbr = &C::bar; // invalid: C is private 
   mbr = &D::baz; // invalid: D is virtual
   mbr = &F::bam; // invalid: conversion isn't defined by the standard
   ...

다른 방향으로의 변환(경유) static_cast)에 의해 관리됩니다 § 5.2.9 9:

"유형의 D 멤버에 대한 포인터" 유형의 rvalue 이력서1 T"는 "B 유형의 멤버에 대한 포인터" 유형의 rvalue로 변환될 수 있습니다. 이력서2 T", 여기서 B는 기본 클래스입니다(절 10 클래스.파생) D의 "T 유형의 B 멤버에 대한 포인터"에서 "T 유형의 D 멤버에 대한 포인터"로의 유효한 표준 변환이 존재하는 경우(4.11 전환율), 그리고 이력서2 이력서 자격과 동일하거나 그보다 더 큰 이력서 자격입니다. 이력서1.11) 널 멤버 포인터 값(4.11 전환율)은 대상 유형의 널 멤버 포인터 값으로 변환됩니다.클래스 B가 원래 멤버를 포함하거나 원래 멤버를 포함하는 클래스의 기본 클래스 또는 파생 클래스인 경우 멤버에 대한 결과 포인터는 원래 멤버를 가리킵니다.그렇지 않으면 캐스트 결과가 정의되지 않습니다.[메모:클래스 B에는 원래 멤버가 포함될 필요가 없지만 멤버에 대한 포인터가 역참조되는 개체의 동적 형식에는 원래 멤버가 포함되어야 합니다.보다 5.5 expr.mptr.oper.]

11) 함수 유형 (멤버 기능 유형에 대한 포인터에 사용 된 기능 포함)은 절대로 CV 자격이되지 않습니다.보다 8.3.5 dcl.fct.

즉, 파생된 형식에서 변환할 수 있습니다. D::* 기지로 B::* 에서 변환할 수 있는 경우 B::*D::*, 하지만 B::* D 유형이거나 D의 자손인 객체에 대해

다른 팁

나는 당신이 요구하는 것이 100% 확실하지 않지만 다음은 가상 기능과 함께 작동하는 예입니다.

#include <iostream>
using namespace std;

class A { 
public:
    virtual void foo() { cout << "A::foo\n"; }
};
class B : public A {
public:
    virtual void foo() { cout << "B::foo\n"; }
};

int main()
{
    void (A::*bar)() = &A::foo;
    (A().*bar)();
    (B().*bar)();
    return 0;
}

멤버에 대한 포인터의 중요한 문제는 포인터가 올바른 유형의 클래스에 대한 모든 참조 또는 포인터에 적용될 수 있다는 것입니다.이 말은 왜냐하면 Z 에서 파생됩니다 Y 포인터(또는 참조) 유형의 포인터(또는 참조) Y 실제로는 기본 클래스 하위 개체를 가리키거나 참조할 수 있습니다. Z 또는 다른 수업 로부터 나오다 Y.

void (Y::*p)() = &Z::z_fn; // illegal

이는 멤버에 대한 포인터에 할당된 모든 것을 의미합니다. Y 실제로 어떤 작업과도 작동해야 합니다 Y.회원을 가리키는 것이 허용된 경우 Z (그 사람은 회원이 아니었어. Y) 그러면 다음의 멤버 함수를 호출하는 것이 가능할 것입니다. Z 실제로는 아니었던 어떤 일에 대해 Z.

반면에, 멤버에 대한 포인터는 Y 또한 Z (상속이란 다음을 의미합니다. Z 기본의 모든 특성과 메서드를 가짐) 포인터를 멤버로 변환하는 것이 적법합니까? Y 멤버에 대한 포인터 Z.이는 본질적으로 안전합니다.

void (Y::*p)() = &Y::y_fn;
void (Z::*q)() = p; // legal and safe

이 기사를 확인하고 싶을 수도 있습니다. 멤버 함수 포인터와 가장 빠른 C++ 대리자 어떤 경우에는 짧은 대답이 '예'인 것 같습니다.

나는 그렇다고 믿는다.함수 포인터는 서명을 사용하여 자신을 식별하므로 기본/파생 동작은 호출한 개체에 따라 달라집니다.

내 실험에서는 다음과 같은 사실이 밝혀졌습니다.경고 - 정의되지 않은 동작일 수 있습니다.누군가가 확실한 참고 자료를 제공할 수 있다면 도움이 될 것입니다.

  1. 이 방법은 효과가 있었지만 파생 멤버 함수를 다음에 할당할 때 캐스트가 필요했습니다. p.
  2. 이 방법도 효과가 있었지만 역참조할 때 추가 캐스트가 필요했습니다. p.

우리가 정말 야심적이라면 다음과 같이 물어볼 수 있습니다. p 관련되지 않은 클래스의 멤버 함수를 가리키는 데 사용할 수 있습니다.나는 그것을 시도하지 않았지만, FastDelegate dagorym의 답변에 링크된 페이지는 그것이 가능함을 시사합니다.

결론적으로 나는 이런 식으로 멤버 함수 포인터를 사용하지 않으려고 노력할 것이다.다음과 같은 구절은 자신감을 불러일으키지 않습니다.

멤버 기능 포인터 사이의 캐스팅은 매우 어두운 지역입니다.C ++의 표준화 중에, 한 클래스에서베이스 또는 파생 클래스의 멤버 함수 포인터로 멤버 기능 포인터를 캐스트 할 수 있는지에 대한 많은 논의가 있었으며 관련된 클래스간에 캐스트 할 수 있는지 여부에 대한 많은 논의가있었습니다.표준위원회가 그들의 마음을 구성 할 때까지, 다른 컴파일러 공급 업체는 이미 이러한 질문에 대한 다른 답변에 그것들을 고정시킨 구현 결정을 내렸다.[FastDelegate 기사]

우리가 class X, class Y : public X, and class Z : public Y

X, Y 모두에 대한 메서드를 void (Y::*p)() 유형의 포인터에 할당할 수 있어야 하지만 Z에 대한 메서드는 할당할 수 없습니다.이유를 알아보려면 다음을 고려하세요.

void (Y::*p)() = &Z::func; // we pretend this is legal
Y * y = new Y; // clearly legal
(y->*p)(); // okay, follows the rules, but what would this mean?

해당 할당을 허용함으로써 우리는 Y 개체에서 Z에 대한 메서드 호출을 허용하여 누가 무엇을 알 수 있는지 알 수 있습니다.포인터를 캐스팅하여 모든 작업을 수행할 수 있지만 이는 안전하지 않거나 작동이 보장되지 않습니다.

다음은 작동하는 예입니다.파생 클래스의 메서드를 재정의할 수 있으며 이 재정의된 메서드에 대한 포인터를 사용하는 기본 클래스의 다른 메서드는 실제로 파생 클래스의 메서드를 호출합니다.

#include <iostream>
#include <string>

using namespace std;

class A {
public:
    virtual void traverse(string arg) {
        find(&A::visit, arg);
    }

protected:
    virtual void find(void (A::*method)(string arg),  string arg) {
        (this->*method)(arg);
    }

    virtual void visit(string arg) {
        cout << "A::visit, arg:" << arg << endl;
    }
};

class B : public A {
protected:
    virtual void visit(string arg) {
        cout << "B::visit, arg:" << arg << endl;
    }
};

int main()
{
    A a;
    B b;
    a.traverse("one");
    b.traverse("two");
    return 0;
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top