C++에서 클래스의 가상 정적 멤버를 시뮬레이션하시겠습니까?

StackOverflow https://stackoverflow.com/questions/34506

  •  09-06-2019
  •  | 
  •  

문제

어쨌든 C++에 일종의 가상 정적 멤버가 있습니까?

예를 들어:

class BaseClass {
    public:
        BaseClass(const string& name) : _name(name) {}
        string GetName() const { return _name; }
        virtual void UseClass() = 0;
    private:
        const string _name;
};


class DerivedClass : public BaseClass {
    public:
        DerivedClass() : BaseClass("DerivedClass") {}
        virtual void UseClass() { /* do something */ }
};

이 예제가 사소한 것이라는 것을 알고 있지만 모든 파생 클래스에 대해 항상 동일하지만 기본 클래스 메서드에서 액세스해야 하는 복잡한 데이터의 벡터가 있다면 어떻게 될까요?

class BaseClass {
    public:
        BaseClass() {}
        virtual string GetName() const = 0;
        virtual void UseClass() = 0;
};


class DerivedClass : public BaseClass {
    public:
        DerivedClass() {}
        virtual string GetName() const { return _name; }
        virtual void UseClass() { /* do something */ }
    private:
        static const string _name;
};

string DerivedClass::_name = "DerivedClass";

모든 클래스에서 _name 멤버와 해당 접근자 GetName()을 다시 구현해야 하기 때문에 이 솔루션은 만족스럽지 않습니다.내 경우에는 _name 동작을 따르는 여러 멤버와 파생 클래스의 10분의 1이 있습니다.

어떤 아이디어?

도움이 되었습니까?

해결책

한 가지 해결책은 다음과 같습니다.

struct BaseData
{
  const string my_word;
  const int my_number;
};

class Base
{
public:
    Base(const BaseData* apBaseData)
    {
        mpBaseData = apBaseData;
    }
    const string getMyWord()
    {
        return mpBaseData->my_word;
    }
    int getMyNumber()
    {
        return mpBaseData->my_number;
    }
private:
    const BaseData* mpBaseData;
};

class Derived : public Base
{
public:
    Derived() : Base(&sBaseData)
    {
    }
private:
    static BaseData sBaseData;
}

BaseData Derived::BaseData = { "Foo", 42 };

다른 팁

질문에 답이 있는 것 같습니다. 제안한 방법이 올바른 방향인 것 같습니다. 단, 공유 멤버가 많은 경우 구조체나 클래스로 모아서 다음과 같이 지나갈 수 있습니다. 기본 클래스의 생성자에 대한 인수입니다.

파생 클래스의 정적 멤버로 구현된 "공유" 멤버를 고집하는 경우 파생 클래스의 코드를 자동 생성할 수 있습니다.XSLT는 간단한 클래스를 자동 생성하는 훌륭한 도구입니다.

일반적으로 이 예제에서는 "가상 정적" 멤버에 대한 필요성을 보여주지 않습니다. 왜냐하면 이와 같은 목적에서는 실제로 상속이 필요하지 않기 때문입니다. 대신 기본 클래스를 사용하고 생성자에서 적절한 값을 허용해야 합니다. 공유 데이터의 중복을 피하기 위해 각 "하위 유형"에 대한 인수의 단일 인스턴스를 생성하고 이에 대한 포인터를 전달합니다.또 다른 유사한 접근 방식은 템플릿을 사용하고 모든 관련 값을 제공하는 클래스를 템플릿 인수로 전달하는 것입니다(이를 일반적으로 "정책" 패턴이라고 함).

결론적으로, 원래 예제의 목적에 따라 이러한 "가상 정적" 멤버는 필요하지 않습니다.작성 중인 코드에 여전히 필요하다고 생각한다면 더 자세히 설명하고 컨텍스트를 추가해 보세요.

위에서 설명한 내용의 예:

class BaseClass {
    public:
        BaseClass(const Descriptor& desc) : _desc(desc) {}
        string GetName() const { return _desc.name; }
        int GetId() const { return _desc.Id; }
        X GetX() connst { return _desc.X; }
        virtual void UseClass() = 0;
    private:
        const Descriptor _desc;
};


class DerivedClass : public BaseClass {
    public:
        DerivedClass() : BaseClass(Descriptor("abc", 1,...)) {}
        virtual void UseClass() { /* do something */ }
};

class DerDerClass : public BaseClass {
    public:
        DerivedClass() : BaseClass("Wowzer", 843,...) {}
        virtual void UseClass() { /* do something */ }
};

이 솔루션에 대해 자세히 설명하고 초기화 해제 문제에 대한 솔루션을 제공하고 싶습니다.

약간만 변경하면 파생 클래스의 각 인스턴스에 대해 "설명자"의 새 인스턴스를 만들지 않고도 위에 설명된 디자인을 구현할 수 있습니다.

각 설명자의 단일 인스턴스를 보유하고 파생 개체를 구성할 때 다음과 같이 사용할 수 있는 싱글톤 개체인 DescriptorMap을 만들 수 있습니다.

enum InstanceType {
    Yellow,
    Big,
    BananaHammoc
}

class DescriptorsMap{
    public:
        static Descriptor* GetDescriptor(InstanceType type) {
            if ( _instance.Get() == null) {
                _instance.reset(new DescriptorsMap());
            }
            return _instance.Get()-> _descriptors[type];
        }
    private:
        DescriptorsMap() {
            descriptors[Yellow] = new Descriptor("Yellow", 42, ...);
            descriptors[Big] = new Descriptor("InJapan", 17, ...)
            ...
        }

        ~DescriptorsMap() {
            /*Delete all the descriptors from the map*/
        }

        static autoptr<DescriptorsMap> _instance;
        map<InstanceType, Descriptor*> _descriptors;
}

이제 우리는 이것을 할 수 있습니다:

class DerivedClass : public BaseClass {
    public:
        DerivedClass() : BaseClass(DescriptorsMap.GetDescriptor(InstanceType.BananaHammoc)) {}
        virtual void UseClass() { /* do something */ }
};

class DerDerClass : public BaseClass {
    public:
        DerivedClass() : BaseClass(DescriptorsMap.GetDescriptor(InstanceType.Yellow)) {}
        virtual void UseClass() { /* do something */ }
};

실행이 끝나면 C 런타임이 초기화 해제를 수행할 때 DescriptorsMap의 인스턴스를 삭제하는 autoptr을 포함한 정적 개체의 소멸자도 호출합니다.

이제 실행이 끝나면 삭제되는 각 설명자의 단일 인스턴스가 있습니다.

파생 클래스의 유일한 목적이 관련 "설명자" 데이터(예:가상 함수를 구현하는 것과 반대로) 기본 클래스를 추상화하지 않고 매번 적절한 설명자를 사용하여 인스턴스를 생성하는 작업을 수행해야 합니다.

나는 템플릿을 "기본 클래스"로 사용하라는 Hershi의 제안에 동의합니다.설명하신 내용에 따르면 서브클래싱보다는 템플릿을 사용하는 것처럼 들립니다.

다음과 같이 템플릿을 생성할 수 있습니다(컴파일을 시도하지 않았습니다):


template <typename T>
class Object
{
public:

  Object( const T& newObject ) : yourObject(newObject) {} ;
  T GetObject() const { return yourObject } ;
  void SetObject( const T& newObject ) { yourObject = newObject } ;

protected:

  const T yourObject ;
} ;

class SomeClassOne
{
public:

  SomeClassOne( const std::vector& someData )
  {
    yourData.SetObject( someData ) ;
  }

private:

  Object<std::vector<int>> yourData ;
} ;

이렇게 하면 템플릿 클래스 메서드를 사용하여 데이터를 사용하고 템플릿 클래스의 다양한 측면을 공유하는 사용자 정의 클래스 내에서 필요에 따라 데이터를 수정할 수 있습니다.

상속을 사용하려는 경우 BaseClass에서 void* 포인터를 사용하고 캐스팅 등을 처리하는 "즐거움"에 의존해야 할 수도 있습니다.

하지만 귀하의 설명에 따르면 상속이 아닌 템플릿이 필요한 것 같습니다.

@허시:이 접근 방식의 문제점은 각 파생 클래스의 각 인스턴스에 데이터 복사본이 있으므로 어떤 면에서는 비용이 많이 들 수 있다는 것입니다.

아마도 이와 같은 것을 시도해 볼 수 있을 것입니다(컴파일 예제 없이 횡설수설하고 있지만 아이디어는 명확해야 합니다).


#include <iostream>
#include <string>
using namespace std;

struct DerivedData
{
  DerivedData(const string & word, const int number) :
    my_word(word), my_number(number) {}
  const string my_word;
  const int my_number;
};

class Base {
public:
  Base() : m_data(0) {}
  string getWord() const { return m_data->my_word; }
  int getNumber() const { return m_data->my_number; }
protected:
  DerivedData * m_data;
};


class Derived : public Base {
public:
  Derived() : Base() {
    if(Derived::s_data == 0) {
      Derived::s_data = new DerivedData("abc", 1);
    }
    m_data = s_data;
  }
private:
  static DerivedData * s_data;
};


DerivedData * Derived::s_data = 0; 

int main()
{
  Base * p_b = new Derived();
  cout getWord() << endl;
}

정적 개체 삭제에 대한 후속 질문에 관해:떠오르는 유일한 해결책은 스마트 포인터를 사용하는 것입니다. 공유 포인터 부스트.

리프 클래스에서 코드를 복제하지 않으려는 것처럼 들리므로 기본 클래스에서 중간 기본 클래스를 파생시키는 것이 좋습니다.이 중간 클래스는 정적 데이터를 보유할 수 있으며 모든 리프 클래스가 중간 기본 클래스에서 파생되도록 할 수 있습니다.이는 파생된 모든 클래스에 대해 하나의 정적 데이터 조각이 필요하다고 가정합니다. 이는 귀하의 예에서 그렇게 보입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top