C ++ 정적 가상 멤버?
그것은 둘 다 멤버 함수가하는 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()
)에서 모두 사용 하는 것이 합리적입니다 . 이는 비교에 유용하고 템플릿에 필수적입니다.
내가 생각할 수있는 유일한 방법은 두 개의 함수 / 함수와 클래스 당 상수를 작성하거나 매크로를 사용하는 것입니다.
다른 솔루션?
아니, 당신이 전화했을 때 무슨 일이 일어날 Object::GetTypeInformation()
까요? 연관된 객체가 없기 때문에 호출 할 파생 클래스 버전을 알 수 없습니다.
제대로 작동하려면 비 정적 가상 기능으로 만들어야합니다. 객체 인스턴스없이 특정 파생 클래스의 버전을 비 가상적으로 호출하려면 두 번째 중복 정적 비가 상 버전도 제공해야합니다.
많은 사람들은 그것이 불가능하다고 말하고 한 걸음 더 나아가 의미가 없다고 말합니다.
정적 멤버는 인스턴스와 관련이 없으며 클래스에만 관련된 것입니다.
가상 멤버는 클래스와 직접 관련이 없으며 인스턴스에만 관련된 것입니다.
따라서 정적 가상 멤버는 인스턴스 또는 클래스와 관련이없는 것입니다.
나는 다른 날 에이 문제에 부딪쳤다 : 정적 클래스로 가득 찬 클래스가 있지만 상속 및 가상 메소드를 사용하고 코드 반복을 줄이고 싶었다. 내 해결책은 다음과 같습니다.
정적 메서드를 사용하는 대신 가상 메서드와 함께 싱글 톤을 사용하십시오.
즉, 각 클래스에는 클래스의 단일 공유 인스턴스에 대한 포인터를 얻기 위해 호출하는 정적 메소드가 포함되어야합니다. 외부 생성자가 추가 인스턴스를 만들어서 잘못 사용할 수 없도록 실제 생성자를 비공개 또는 보호로 만들 수 있습니다.
실제로 싱글 톤을 사용하는 것은 상속과 가상 메소드를 활용할 수 있다는 점을 제외하면 정적 메소드를 사용하는 것과 매우 유사합니다.
것이 가능하다!
그러나 정확히 가능한 것은 좁혀 봅시다. 사람들은 종종 정적 호출 "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()
장점 :
- 코드 중복 감소 (그러나 모든 생성자에서 OVERRIDE_STATIC_FUNCTIONS를 호출해야 함)
단점 :
- 모든 생성자에서 OVERRIDE_STATIC_FUNCTIONS
- 메모리 및 성능 오버 헤드
- 복잡성 증가
미결 문제 :
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를 암시 적으로 호출하는 방법은 무엇입니까?
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";
// ...
};
것이 가능하다. 정적 및 가상의 두 가지 기능 만들기
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
};
정적 멤버 함수에 this
포인터가 없기 때문에 불가능합니다 . 그리고 정적 멤버 (함수와 변수 모두)는 실제로 클래스 멤버가 아닙니다. 방금 호출 ClassName::member
하여 클래스 액세스 지정자를 준수합니다. 스토리지는 클래스 외부에 정의되어 있습니다. 클래스의 객체를 인스턴스화 할 때마다 스토리지가 생성되지 않습니다. 클래스 멤버에 대한 포인터는 의미론과 구문에서 특별합니다. 정적 멤버에 대한 포인터는 모든 점에서 일반적인 포인터입니다.
클래스의 가상 함수에는 this
포인터 가 필요하며 클래스 에 매우 결합되어 정적 일 수 없습니다.
글쎄, 꽤 늦은 답변이지만 호기심이 반복되는 템플릿 패턴을 사용하는 것이 가능합니다. 이 위키 백과 기사에는 필요한 정보가 있으며 정적 다형성 아래의 예가 요청됩니다.
아니요, 정적 멤버 함수는 가상이 될 수 없습니다. 가상 개념은 vptr의 도움으로 런타임에 해결되므로 vptr은 클래스의 정적 멤버가 아닙니다. 정적 멤버 함수는 vptr을 사용할 수 없으므로 정적 멤버가 될 수 있기 때문에 가상이 아닙니다.
나는 당신이하려는 일이 템플릿을 통해 이루어질 수 있다고 생각합니다. 여기 줄 사이를 읽으려고합니다. 당신이하려고하는 것은 파생 된 코드를 호출하지만 호출자는 어떤 클래스를 지정하지 않는 일부 코드에서 메소드를 호출하는 것입니다. 예:
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>();
}
정적 멤버는 컴파일 타임에 바인드되고 가상 멤버는 런타임에 바인드되므로 불가능합니다.
첫째, 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();
}
};
가능하지 않지만 그것은 단지 누락 때문입니다. 많은 사람들이 주장하는 것처럼 "의미가없는"것은 아닙니다. 분명히하기 위해, 나는 이것에 대해 이야기하고 있습니다 :
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 % 무언가 (단지 그렇지 않은 것)이며 유용한 무언가를 주장 할 것입니다.
일반적인 가상 기능의 작동 방식을 고려하십시오. static
s를 제거하고 다른 것들을 추가하면 다음이 있습니다.
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
는 없지만 성능에 측정 가능한 차이를 만들지 않는다는 것입니다.
즉, 인스턴스가 있고 인스턴스가없는 경우에 대해 별도의 정적 및 비 정적 기능이 없지만 사용 할 때 실제로 "가상"이라는 것이 혼란 스러울 수 있습니다 인스턴스 호출.
C ++를 사용하면 crt 메소드와 함께 정적 상속을 사용할 수 있습니다. 예를 들어, 창 템플릿 atl & wtl에서 널리 사용됩니다.
https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern을 참조 하십시오
간단하게하기 위해, 클래스 myclass : public myancestor와 같이 자체적으로 템플릿 화 된 클래스가 있습니다. 이 시점에서 myancestor 클래스는 이제 정적 T :: YourImpl 함수를 호출 할 수 있습니다.
어쩌면 아래에서 내 솔루션을 시도해 볼 수 있습니다.
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;
}
다른 사람들이 말했듯이 두 가지 중요한 정보가 있습니다.
this
정적 함수 호출을 할 때 포인터 가 없으며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;
}
참고 URL : https://stackoverflow.com/questions/1820477/c-static-virtual-members
'Programing' 카테고리의 다른 글
동시 해시 세트 (0) | 2020.07.01 |
---|---|
Github 리포지토리에서 "git update-server-info를 실행 했습니까?"오류 (0) | 2020.07.01 |
Node.js 애플리케이션을 단일 실행 파일로 어떻게 배치합니까? (0) | 2020.06.30 |
홀수 업데이트와 Java 업데이트의 차이점은 무엇입니까? (0) | 2020.06.30 |
Swift의 프로토콜과 Java의 인터페이스 비교 (0) | 2020.06.30 |