C ++ 정적 가상 멤버?


140

그것은 둘 다 멤버 함수가하는 C ++로 가능 static하고를 virtual? 분명히 그것을 할 수있는 간단한 방법은 없지만 ( static virtual member();컴파일 오류입니까) 적어도 동일한 효과를 얻을 수있는 방법이 있습니까?

IE :

struct Object
{
     struct TypeInformation;

     static virtual const TypeInformation &GetTypeInformation() const;
};

struct SomeObject : public Object
{
     static virtual const TypeInformation &GetTypeInformation() const;
};

GetTypeInformation()인스턴스 ( object->GetTypeInformation())와 클래스 ( SomeObject::GetTypeInformation()) 모두에서 사용 하는 것이 합리적입니다 . 이는 비교에 유용하고 템플릿에 필수적입니다.

내가 생각할 수있는 유일한 방법은 두 개의 함수 / 함수와 클래스 당 상수를 작성하거나 매크로를 사용하는 것입니다.

다른 솔루션?


12
단지 측면 주석 : 정적 메소드는 어떤 인스턴스에서도 실행되지 않으므로 암시 적 this 포인터가 없습니다. 즉 const, 메서드 시그니처에서는 암시 적 this포인터를 상수로 플래그 지정하고 암시 적 매개 변수가 없기 때문에 정적 메서드에는 적용 할 수 없습니다.
David Rodríguez-dribeas

2
@ cvb : 예제를 리플렉션이없는 코드로 바꾸는 것을 진지하게 다시 생각할 것입니다. 이제는 서로 관련이있는 두 가지 문제를 구분하고 있습니다. 네, 당신이 요청한지 5 년 반이 지났습니다.
einpoklum

여기에 암시 적으로 필요한 기능 중 하나는 컴파일러가 계층의 각 개체가 특정 인터페이스 (하나 이상의 메서드가 정적 인)를 구현하는지 확인하도록하는 것입니다. 정적 메서드를 추가하는 것을 잊어 버린 경우 컴파일러에서 오류 발생 하므로 기본적으로 정적 메서드에 대한 순수한 가상 검사는 의미가 있습니다 . virtual은 여기서 키워드가 아니며, 이 특정 경우를 제외하고 C ++에서 동의어가되는 추상적 입니다. 불행히도 현재 C ++로는 할 수 없습니다.
xryl669

답변:


75

아니요, 전화 할 때 어떤 일이 발생 Object::GetTypeInformation()합니까? 연관된 객체가 없기 때문에 호출 할 파생 클래스 버전을 알 수 없습니다.

제대로 작동하려면 비 정적 가상 기능으로 만들어야합니다. 객체 인스턴스없이 비 파생적으로 특정 파생 클래스의 버전을 호출하려면 두 번째 중복 정적 비가 상 버전도 제공해야합니다.


8
정적 클래스 (또는 클래스 정적 멤버)를 싱글 톤으로 생각하면 기본 클래스 인스턴스 에서 일반 가상 메소드를 호출하는 것과 동일한 방식으로 Object :: GetTypeInformation을 호출해야 합니다 . (물론 C ++에서 가상 정적 메소드를 지원 한 경우 )
Spook

13
그것은 완전히 논쟁적인 주장입니다. 객체 대신 클래스를 사용하면 가상 디스패치 대신 해당 클래스의 버전을 자연스럽게 사용합니다. 거기에 새로운 것은 없습니다.
중복 제거기

54

많은 사람들은 그것이 불가능하다고 말하고 한 걸음 더 나아가 의미가 없다고 말합니다.

정적 멤버는 인스턴스와 관련이 없으며 클래스에만 관련된 것입니다.

가상 멤버는 클래스와 직접 관련이 없으며 인스턴스에만 관련된 것입니다.

따라서 정적 가상 멤버는 인스턴스 또는 클래스와 관련이없는 것입니다.


42
클래스가 일류 값인 언어에서는 완벽하게 의미가 있습니다. 예를 들어 델파이는이를 가지고 있으며 "정적 가상"메소드도 있습니다.
Pavel Minaev

4
바로 그거죠. "가상 함수"는 정의 에 따라 동적으로 링크 되는 함수입니다 . 즉 , 주어진 객체 의 동적 유형 에 따라 런타임에 선택됩니다 . 따라서 객체 없음 = 가상 통화 없음.
코스

7
또한 정적 가상은 의미가 있다고 생각합니다. 인터페이스 클래스를 정의하고 파생 클래스에서 구현해야하는 정적 메소드를 포함 할 수 있습니다.
bkausbk

34
그것은을위한 매우 의미가 아니다 static virtual방법,하지만 static 순수한 virtual 방법은 인터페이스에서 매우 의미가 있습니다.
Bret Kuhns

4
을 갖는 것은 완벽하게 의미가 있습니다 static const string MyClassSillyAdditionalName.
einpoklum

23

나는 다른 날 에이 문제에 부딪쳤다 : 정적 클래스로 가득 찬 클래스가 있지만 상속 및 가상 메소드를 사용하고 코드 반복을 줄이고 싶었다. 내 해결책은 다음과 같습니다.

정적 메서드를 사용하는 대신 가상 메서드와 함께 싱글 톤을 사용하십시오.

즉, 각 클래스에는 클래스의 단일 공유 인스턴스에 대한 포인터를 얻기 위해 호출하는 정적 메소드가 포함되어야합니다. 외부 생성자가 추가 인스턴스를 만들어서 잘못 사용할 수 없도록 실제 생성자를 비공개 또는 보호로 만들 수 있습니다.

실제로 싱글 톤을 사용하는 것은 상속과 가상 메소드를 활용할 수 있다는 점을 제외하면 정적 메소드를 사용하는 것과 매우 유사합니다.


컴파일러가 다음과 같은 사실을 확신 할 수 없다면 성능이 떨어질 것입니다. 1. 실제로 싱글 톤이고 2. 아무것도 상속받지 않습니다. 모든 오버 헤드를 최적화 할 수 있다고 생각하지 않습니다.
einpoklum

이런 종류의 성능이 걱정된다면 C #은 아마도 잘못된 언어 일 것입니다.
네이트 CK

3
아, 좋은 지적입니다. 분명히 제가 2009 년에 쓴 이후로 이것에 대해 생각한 이후로 오랜 시간이 지났습니다. 다시 한 번 말해 보도록하겠습니다. 이런 종류의 성능이 걱정된다면 상속의 사용을 완전히 피해야 할 것입니다. 포스터는 구체적으로 가상 메소드를 요청했기 때문에 가상 메소드의 오버 헤드에 대해 불평하기 위해 여기 온 것이 이상합니다.
Nate CK

15

것이 가능하다!

그러나 정확히 가능한 것은 좁혀 봅시다. 사람들은 종종 정적 호출 "SomeDerivedClass :: myfunction ()"및 다형성 호출 "base_class_pointer-> myfunction ()"을 통해 동일한 함수를 호출하는 데 필요한 코드 복제로 인해 일종의 "정적 가상 함수"를 원합니다. 이러한 기능을 허용하는 "법적"방법은 기능 정의의 복제입니다.

class Object
{
public:
    static string getTypeInformationStatic() { return "base class";}
    virtual string getTypeInformation() { return getTypeInformationStatic(); }
}; 
class Foo: public Object
{
public:
    static string getTypeInformationStatic() { return "derived class";}
    virtual string getTypeInformation() { return getTypeInformationStatic(); }
};

기본 클래스에 많은 수의 정적 함수가 있고 파생 클래스가 모든 함수를 재정의해야하고 가상 함수에 대한 중복 정의를 제공하지 못한 경우 어떻게해야합니까? 맞습니다. 런타임 중에 추적하기 어려운 이상한 오류가 발생 합니다 . 코드 중복은 나쁜 일입니다. 다음은이 문제를 해결하려고 시도합니다 (그리고 나는 그것이 완전히 타입 안전하고 typeid 또는 dynamic_cast와 같은 흑 마법을 포함하지 않는다고 미리 말하고 싶습니다 :)

따라서 파생 클래스 당 하나의 getTypeInformation () 정의 만 제공하려고하며 정적 정의 여야합니다.getTypeInformation ()이 가상 인 경우 "SomeDerivedClass :: getTypeInformation ()"을 호출 할 수 없기 때문에 함수. 기본 클래스에 대한 포인터를 통해 파생 클래스의 정적 함수를 어떻게 호출 할 수 있습니까? vtable은 가상 함수에만 포인터를 저장하기 때문에 vtable에서는 불가능하며 가상 함수를 사용하지 않기로 결정했기 때문에 vtable을 수정하기 위해 수정할 수 없습니다. 그런 다음 기본 클래스에 대한 포인터를 통해 파생 클래스의 정적 함수에 액세스하려면 기본 클래스 내에 객체 유형을 저장해야합니다. 한 가지 방법은 "호 기적으로 반복되는 템플릿 패턴"을 사용하여 기본 클래스를 템플릿 화하는 것입니다. 그러나 여기서는 적합하지 않으며 "타입 삭제"라는 기술을 사용합니다.

class TypeKeeper
{
public:
    virtual string getTypeInformation() = 0;
};
template<class T>
class TypeKeeperImpl: public TypeKeeper
{
public:
    virtual string getTypeInformation() { return T::getTypeInformationStatic(); }
};

이제 변수 "keeper"를 사용하여 기본 클래스 "Object"내에 객체 유형을 저장할 수 있습니다.

class Object
{
public:
    Object(){}
    boost::scoped_ptr<TypeKeeper> keeper;

    //not virtual
    string getTypeInformation() const 
    { return keeper? keeper->getTypeInformation(): string("base class"); }

};

파생 클래스 키퍼는 생성하는 동안 초기화해야합니다.

class Foo: public Object
{
public:
    Foo() { keeper.reset(new TypeKeeperImpl<Foo>()); }
    //note the name of the function
    static string getTypeInformationStatic() 
    { return "class for proving static virtual functions concept"; }
};

구문 설탕을 추가합시다 :

template<class T>
void override_static_functions(T* t)
{ t->keeper.reset(new TypeKeeperImpl<T>()); }
#define OVERRIDE_STATIC_FUNCTIONS override_static_functions(this)

자손 선언은 다음과 같습니다.

class Foo: public Object
{
public:
    Foo() { OVERRIDE_STATIC_FUNCTIONS; }
    static string getTypeInformationStatic() 
    { return "class for proving static virtual functions concept"; }
};

class Bar: public Foo
{
public:
    Bar() { OVERRIDE_STATIC_FUNCTIONS; }
    static string getTypeInformationStatic() 
    { return "another class for the same reason"; }
};

용법:

Object* obj = new Foo();
cout << obj->getTypeInformation() << endl;  //calls Foo::getTypeInformationStatic()
obj = new Bar();
cout << obj->getTypeInformation() << endl;  //calls Bar::getTypeInformationStatic()
Foo* foo = new Bar();
cout << foo->getTypeInformation() << endl; //calls Bar::getTypeInformationStatic()
Foo::getTypeInformation(); //compile-time error
Foo::getTypeInformationStatic(); //calls Foo::getTypeInformationStatic()
Bar::getTypeInformationStatic(); //calls Bar::getTypeInformationStatic()

장점 :

  1. 코드 중복 감소 (그러나 모든 생성자에서 OVERRIDE_STATIC_FUNCTIONS를 호출해야 함)

단점 :

  1. 모든 생성자에서 OVERRIDE_STATIC_FUNCTIONS
  2. 메모리 및 성능 오버 헤드
  3. 복잡성 증가

미결 문제 :

1) 정적 함수와 가상 함수의 이름이 여기에서 모호성을 해결하는 방법이 있습니까?

class Foo
{
public:
    static void f(bool f=true) { cout << "static";}
    virtual void f() { cout << "virtual";}
};
//somewhere
Foo::f(); //calls static f(), no ambiguity
ptr_to_foo->f(); //ambiguity

2) 모든 생성자 내에서 OVERRIDE_STATIC_FUNCTIONS를 암시 적으로 호출하는 방법은 무엇입니까?


노력에 +1하지만, 가상 메소드를 사용하여 기능을 싱글 톤에 위임하는 것보다 더 우아하다는 것은 확실하지 않습니다.
einpoklum

1
@einpoklum, 이것이 바람직 할 수있는 상황을 생각할 수 있습니다. 이미 정적 메소드를 호출하는 많은 클라이언트 코드가 있다고 가정하십시오. 가상 메소드를 사용하여 정적 메소드에서 싱글 톤으로 전환하려면 클라이언트 코드를 변경해야하지만 위에 제시된 솔루션은 비 침습적입니다.
Alsk

"Foo :: getTypeInformation"및 "TypeKeeperImpl :: getTypeInformation"에는 "virtual"키워드가 필요하지 않습니다.
bartolo-otrit

12

Alsk는 이미 매우 자세한 답변을 제공했지만 개선 된 구현이 너무 복잡하다고 생각하기 때문에 대안을 추가하고 싶습니다.

모든 객체 유형에 대한 인터페이스를 제공하는 추상 기본 클래스로 시작합니다.

class Object
{
public:
    virtual char* GetClassName() = 0;
};

이제 실제 구현이 필요합니다. 그러나 정적 메소드와 가상 메소드를 모두 작성하지 않아도되도록 실제 오브젝트 클래스가 가상 메소드를 상속하게됩니다. 기본 클래스가 정적 멤버 함수에 액세스하는 방법을 알고있는 경우에만 작동합니다. 따라서 템플릿을 사용하고 실제 객체 클래스 이름을 전달해야합니다.

template<class ObjectType>
class ObjectImpl : public Object
{
public:
    virtual char* GetClassName()
    {
        return ObjectType::GetClassNameStatic();
    }
};

마지막으로 실제 객체를 구현해야합니다. 여기서는 정적 멤버 함수 만 구현하면됩니다. 가상 멤버 함수는 ObjectImpl 템플리트 클래스에서 상속되며 파생 클래스의 이름으로 인스턴스화되므로 정적 멤버에 액세스합니다.

class MyObject : public ObjectImpl<MyObject>
{
public:
    static char* GetClassNameStatic()
    {
        return "MyObject";
    }
};

class YourObject : public ObjectImpl<YourObject>
{
public:
    static char* GetClassNameStatic()
    {
        return "YourObject";
    }
};

테스트 할 코드를 추가하겠습니다 :

char* GetObjectClassName(Object* object)
{
    return object->GetClassName();
}

int main()
{
    MyObject myObject;
    YourObject yourObject;

    printf("%s\n", MyObject::GetClassNameStatic());
    printf("%s\n", myObject.GetClassName());
    printf("%s\n", GetObjectClassName(&myObject));
    printf("%s\n", YourObject::GetClassNameStatic());
    printf("%s\n", yourObject.GetClassName());
    printf("%s\n", GetObjectClassName(&yourObject));

    return 0;
}

부록 (2019 년 1 월 12 일) :

GetClassNameStatic () 함수를 사용하는 대신 클래스 이름을 정적 멤버, 심지어 "인라인"으로 정의 할 수 있습니다. "인라인"도 C ++ 11 이후 IIRC에서 작동합니다 (모든 수정 자에 의해 겁 먹지 마십시오).

class MyObject : public ObjectImpl<MyObject>
{
public:
    // Access this from the template class as `ObjectType::s_ClassName` 
    static inline const char* const s_ClassName = "MyObject";

    // ...
};

11

것이 가능하다. 정적 및 가상의 두 가지 기능 만들기

struct Object{     
  struct TypeInformation;
  static  const TypeInformation &GetTypeInformationStatic() const 
  { 
      return GetTypeInformationMain1();
  }
  virtual const TypeInformation &GetTypeInformation() const
  { 
      return GetTypeInformationMain1();
  }
protected:
  static const TypeInformation &GetTypeInformationMain1(); // Main function
};

struct SomeObject : public Object {     
  static  const TypeInformation &GetTypeInformationStatic() const 
  { 
      return GetTypeInformationMain2();
  }
  virtual const TypeInformation &GetTypeInformation() const
  { 
      return GetTypeInformationMain2();
  }
protected:
  static const TypeInformation &GetTypeInformationMain2(); // Main function
};

4
또한 정적 메소드는 const 일 수 없습니다. 그것은 말이되지 않습니다, 그들은 어떤 인스턴스를 돌연변이시키지 않을 것입니까?
David Rodríguez-dribeas

1
이것은 대부분 코드 복제입니다. 서브 클래스는 정적 const 멤버 만 있으면되고, 코드에 액세스 할 필요는 없습니다.
einpoklum

8

정적 멤버 함수에 this포인터가 없기 때문에 불가능합니다 . 그리고 정적 멤버 (함수와 변수 모두)는 실제로 클래스 멤버가 아닙니다. 방금 호출 ClassName::member하여 클래스 액세스 지정자를 준수합니다. 스토리지는 클래스 외부에 정의되어 있습니다. 클래스의 객체를 인스턴스화 할 때마다 스토리지가 생성되지 않습니다. 클래스 멤버에 대한 포인터는 의미론과 구문에서 특별합니다. 정적 멤버에 대한 포인터는 모든 점에서 일반적인 포인터입니다.

클래스의 가상 함수에는 this포인터 가 필요하며 클래스 에 매우 결합되어 정적 일 수 없습니다.


1
비 정적 함수 만 this 포인터 가 필요합니다 . 정적 함수는 인스턴스에 국한되지 않으며 필요하지 않습니다. 따라서 가상 정적 멤버가 불가능한 이유는 아닙니다.
einpoklum

7

글쎄, 꽤 늦은 답변이지만 호기심이 반복되는 템플릿 패턴을 사용하는 것이 가능합니다. 이 위키 백과 기사에는 필요한 정보가 있으며 정적 다형성 아래의 예가 요청됩니다.


3

나는 당신이하려는 일이 템플릿을 통해 이루어질 수 있다고 생각합니다. 여기 줄 사이를 읽으려고합니다. 당신이하려고하는 것은 파생 된 코드를 호출하지만 호출자는 어떤 클래스를 지정하지 않는 일부 코드에서 메소드를 호출하는 것입니다. 예:

class Foo {
public:
    void M() {...}
};

class Bar : public Foo {
public:
    void M() {...}
};

void Try()
{
    xxx::M();
}

int main()
{
    Try();
}

Try ()가 Bar를 지정하지 않고 M의 Bar 버전을 호출하려고합니다. 정적 처리 방법은 템플릿을 사용하는 것입니다. 따라서 다음과 같이 변경하십시오.

class Foo {
public:
    void M() {...}
};

class Bar : public Foo {
public:
    void M() {...}
};

template <class T>
void Try()
{
    T::M();
}

int main()
{
    Try<Bar>();
}

1
코드를 4 칸 들여 쓰기하면 자동으로 포맷 할 수 있습니다. 또는 동일한 용도의 인라인을 달성하기 위해 백 틱을 사용할 수 있다고 생각합니다.
chollida 2016

1
이것은 내가 놓친 것입니다. 감사합니다. 여전히 음모 가 이상합니다.
allesblinkt

M ()은 정적 함수가 아닙니다. 어떻게 T :: M ()이라고 불리는가?
DDukDDak99

3

아니요, 정적 멤버 함수는 가상이 될 수 없습니다. 가상 개념은 런타임에 vptr을 사용하여 해결되고 vptr은 클래스의 정적 멤버가 아니기 때문에 정적 멤버 함수는 vptr을 사용할 수 없으므로 정적 멤버가 될 수 있기 때문에 가상이 아닙니다.


2
인스턴스 별 가상 메서드에만 인스턴스의 vtable이 필요합니다. 정적-클래스 당-vtable을 가질 수 있습니다. 그리고 인스턴스에 대해 알고 싶다면 인스턴스의 vtable에서 statics vtable 클래스를 가리 키십시오.
einpoklum

2

가능하지 않지만 그것은 단지 누락 때문입니다. 많은 사람들이 주장하는 것처럼 "이해가되지 않는"것은 아닙니다. 분명히하기 위해, 나는 이것에 대해 이야기하고 있습니다 :

struct Base {
  static virtual void sayMyName() {
    cout << "Base\n";
  }
};

struct Derived : public Base {
  static void sayMyName() override {
    cout << "Derived\n";
  }
};

void foo(Base *b) {
  b->sayMyName();
  Derived::sayMyName(); // Also would work.
}

이것은 구현 될 수 있는 100 % 무언가 (단지 그렇지 않은 것)이며 유용한 무언가를 주장 할 것입니다.

일반적인 가상 기능의 작동 방식을 고려하십시오. statics를 제거하고 다른 것들을 추가하면 다음이 있습니다.

struct Base {
  virtual void sayMyName() {
    cout << "Base\n";
  }
  virtual void foo() {
  }
  int somedata;
};

struct Derived : public Base {
  void sayMyName() override {
    cout << "Derived\n";
  }
};

void foo(Base *b) {
  b->sayMyName();
}

이것은 잘 작동하며 기본적으로 컴파일러는 VTables라는 두 테이블을 만들고 가상 함수에 인덱스를 다음과 같이 할당합니다.

enum Base_Virtual_Functions {
  sayMyName = 0;
  foo = 1;
};

using VTable = void*[];

const VTable Base_VTable = {
  &Base::sayMyName,
  &Base::foo
};

const VTable Derived_VTable = {
  &Derived::sayMyName,
  &Base::foo
};

다음으로 가상 함수가있는 각 클래스에는 VTable을 가리키는 다른 필드가 추가되므로 컴파일러는 기본적으로 다음과 같이 변경합니다.

struct Base {
  VTable* vtable;
  virtual void sayMyName() {
    cout << "Base\n";
  }
  virtual void foo() {
  }
  int somedata;
};

struct Derived : public Base {
  VTable* vtable;
  void sayMyName() override {
    cout << "Derived\n";
  }
};

그러면 실제로 전화하면 어떻게됩니까 b->sayMyName()? 기본적으로 이것은 :

b->vtable[Base_Virtual_Functions::sayMyName](b);

첫 번째 매개 변수는 this입니다.

좋아, 정적 가상 함수와 어떻게 작동합니까? 정적 멤버 함수와 비 정적 멤버 함수의 차이점은 무엇입니까? 유일한 차이점은 후자가 this포인터를 얻는다는 것 입니다.

정적 가상 함수로 정확히 동일한 작업을 수행 할 수 있습니다 this. 포인터 만 제거하면 됩니다.

b->vtable[Base_Virtual_Functions::sayMyName]();

그러면 두 구문을 모두 지원할 수 있습니다.

b->sayMyName(); // Prints "Base" or "Derived"...
Base::sayMyName(); // Always prints "Base".

따라서 모든 naysayers를 무시하십시오. 그것은 않습니다 메이크업 감각. 왜 지원되지 않습니까? 나는 그것이 이익이 거의 없으며 약간 혼란 스러울 수 있기 때문이라고 생각합니다.

일반적인 가상 기능에 대한 유일한 기술적 이점은 기능에 전달할 필요 this는 없지만 성능에 측정 가능한 차이를 만들지 않는다는 것입니다.

즉, 인스턴스가 있거나 인스턴스가없는 경우에 대해 별도의 정적 및 비 정적 함수가 없으며 사용시 실제로 "가상"이라고 혼동 할 수도 있습니다. 인스턴스 호출.


0

정적 멤버는 컴파일 타임에 바인딩되는 반면 가상 멤버는 런타임에 바인딩되므로 불가능합니다.


0

첫째, OP가 요청하는 것이 모순이라는 점은 정답이다. 가상 메소드는 인스턴스의 런타임 유형에 의존한다. 정적 함수는 특히 인스턴스에 의존하지 않고 유형에만 의존합니다. 즉, 정적 함수가 유형에 특정한 것을 반환하는 것이 합리적입니다. 예를 들어, State 패턴에 대한 MouseTool 클래스 제품군이 있었고 각각의 키보드 수정자를 반환하는 정적 함수를 갖기 시작했습니다. 올바른 MouseTool 인스턴스를 만든 팩토리 함수에서 정적 함수를 사용했습니다. 이 함수는 MouseToolA :: keyboardModifier (), MouseToolB :: keyboardModifier () 등에 대해 마우스 상태를 확인한 후 적절한 인스턴스를 인스턴스화했습니다. 물론 나중에 상태가 올바른지 확인하고 싶었습니다. "

따라서 자신이 이것을 원한다면 해결책을 바꾸고 싶을 수도 있습니다. 여전히 정적 메소드를 갖고 인스턴스의 동적 유형에 따라 동적으로 호출하려는 요구를 이해합니다. 방문자 패턴 이 원하는 것을 줄 수 있다고 생각합니다 . 원하는 것을 제공합니다. 약간의 추가 코드이지만 다른 방문자에게 유용 할 수 있습니다.

배경 은 http://en.wikipedia.org/wiki/Visitor_pattern 을 참조하십시오 .

struct ObjectVisitor;

struct Object
{
     struct TypeInformation;

     static TypeInformation GetTypeInformation();
     virtual void accept(ObjectVisitor& v);
};

struct SomeObject : public Object
{
     static TypeInformation GetTypeInformation();
     virtual void accept(ObjectVisitor& v) const;
};

struct AnotherObject : public Object
{
     static TypeInformation GetTypeInformation();
     virtual void accept(ObjectVisitor& v) const;
};

그런 다음 각 구체적인 Object에 대해 :

void SomeObject::accept(ObjectVisitor& v) const {
    v.visit(*this); // The compiler statically picks the visit method based on *this being a const SomeObject&.
}
void AnotherObject::accept(ObjectVisitor& v) const {
    v.visit(*this); // Here *this is a const AnotherObject& at compile time.
}

그런 다음 기본 방문자를 정의하십시오.

struct ObjectVisitor {
    virtual ~ObjectVisitor() {}
    virtual void visit(const SomeObject& o) {} // Or = 0, depending what you feel like.
    virtual void visit(const AnotherObject& o) {} // Or = 0, depending what you feel like.
    // More virtual void visit() methods for each Object class.
};

그런 다음 적절한 정적 기능을 선택하는 콘크리트 방문자 :

struct ObjectVisitorGetTypeInfo {
    Object::TypeInformation result;
    virtual void visit(const SomeObject& o) {
        result = SomeObject::GetTypeInformation();
    }
    virtual void visit(const AnotherObject& o) {
        result = AnotherObject::GetTypeInformation();
    }
    // Again, an implementation for each concrete Object.
};

마지막으로 사용하십시오.

void printInfo(Object& o) {
    ObjectVisitorGetTypeInfo getTypeInfo;
    Object::TypeInformation info = o.accept(getTypeInfo).result;
    std::cout << info << std::endl;
}

노트:

  • 일관성은 운동으로 남았습니다.
  • 정적에서 참조를 반환했습니다. 싱글 톤이 없다면 의심의 여지가 있습니다.

방문 메소드 중 하나가 잘못된 정적 함수를 호출하는 복사-붙여 넣기 오류를 피하려면 다음과 같은 템플리트로 방문자에게 템플리트 화 된 헬퍼 함수 (가상적으로는 불가능 함)를 사용할 수 있습니다.

struct ObjectVisitorGetTypeInfo {
    Object::TypeInformation result;
    virtual void visit(const SomeObject& o) { doVisit(o); }
    virtual void visit(const AnotherObject& o) { doVisit(o); }
    // Again, an implementation for each concrete Object.

  private:
    template <typename T>
    void doVisit(const T& o) {
        result = T::GetTypeInformation();
    }
};

가상 정적 메소드는 존재 했더라도 인스턴스의 어떤 것에도 의존하지 않지만 인스턴스를 호출하려면 해당 유형을 알아야합니다. 이것은 컴파일러에 의해 해결 될 수 있습니다 (예를 들어 가상 정적 메소드 및 멤버에 대한 포인터와 함께 클래스 별 단일 데이터 구조를 사용하여). 그것은 분명히 모순이 아닙니다.
einpoklum

용어의 모순인지 여부는 의미론의 문제입니다. 인스턴스에서 정적 호출을 허용하는 C ++을 상상할 수 있습니다 (예 : Foo foo; ... foo::bar();대신 Foo::bar();). 그것은 다르지 decltype(foo)::bar();않지만 다시 정적으로 묶일 것입니다. 방문자 접근 방식은 정적 메소드를 가상 const 메소드로 만들지 않고이 동작을 수행하는 합리적인 방법으로 보입니다.
Ben

0

C ++에서는 crt 메소드와 함께 정적 상속을 사용할 수 있습니다. 예를 들어, 창 템플릿 atl & wtl에서 널리 사용됩니다.

https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern을 참조 하십시오

간단하게하기 위해, 클래스 myclass : public myancestor와 같이 자체적으로 템플릿 화 된 클래스가 있습니다. 이 시점에서 myancestor 클래스는 이제 정적 T :: YourImpl 함수를 호출 할 수 있습니다.


-1

어쩌면 아래에서 내 솔루션을 시도해 볼 수 있습니다.

class Base {
public:
    Base(void);
    virtual ~Base(void);

public:
    virtual void MyVirtualFun(void) = 0;
    static void  MyStaticFun(void) { assert( mSelf != NULL); mSelf->MyVirtualFun(); }
private:
    static Base* mSelf;
};

Base::mSelf = NULL;

Base::Base(void) {
    mSelf = this;
}

Base::~Base(void) {
    // please never delete mSelf or reset the Value of mSelf in any deconstructors
}

class DerivedClass : public Base {
public:
    DerivedClass(void) : Base() {}
    ~DerivedClass(void){}

public:
    virtual void MyVirtualFun(void) { cout<<"Hello, it is DerivedClass!"<<endl; }
};

int main() {
    DerivedClass testCls;
    testCls.MyStaticFun(); //correct way to invoke this kind of static fun
    DerivedClass::MyStaticFun(); //wrong way
    return 0;
}

그래, 나도 알아 4 년 많은 세부 사항에서 코드를 읽고 싶지 않은 사람들을 위해-점수를 설명하십시오. 인스턴스가 파괴 된 경우에도Base::mSelf 파생 클래스의 가장 최근에 생성 된 인스턴스를 나타냅니다 . 그래서 원하는 것이 아닙니다. class D1 : public Base ...; class D2 : public Base ...; ...; D1* pd1 = new D1(); D2* pd2 = new D2(); pd1->MyStaticFun(); /* calls D2::MyVirtualFun() */ delete pd2; pd1->MyStaticFun(); /* calls via deleted pd2 */
Jesse Chisholm

-3

다른 사람들이 말했듯이 두 가지 중요한 정보가 있습니다.

  1. this정적 함수 호출을 할 때 포인터 가 없으며
  2. this가상 테이블 또는 썽크가 호출하는 실행 방법을 찾기 위해 사용되는 구조체의 포인터를 가리 킵니다.

정적 함수는 컴파일 타임에 결정됩니다.

클래스의 C ++ 정적 멤버 에서이 코드 예제를 보여주었습니다 . 널 포인터가 주어지면 정적 메소드를 호출 할 수 있음을 보여줍니다.

struct Foo
{
    static int boo() { return 2; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    Foo* pFoo = NULL;
    int b = pFoo->boo(); // b will now have the value 2
    return 0;
}

6
기술적으로 이것은 정의되지 않은 동작입니다. 어떤 이유로 든 널 포인터를 지연시킬 수 없습니다. 널 포인터로 할 수있는 유일한 것은 a) 다른 포인터를 할당하고 b) 다른 포인터와 비교하는 것입니다.
KeithB

1
또한, 당신은 단지 그것을 비교할 수 있습니다 어떤지 . 또 다른 포인터가 아닌 주문 즉와 (또는 inequality_ p < null, p >= null등이 잘 정의되지 않은 있습니다.
파벨 Minaev을

1
@KeithB-완전성을 위해 널 포인터에서 delete를 안전하게 호출 할 수도 있습니다.
Steve Rowe
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.