cvb*_*cvb 133 c++ virtual static
在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;
};
Run Code Online (Sandbox Code Playgroud)
这是有道理的使用GetTypeInformation()
上的一个实例(都object->GetTypeInformation()
)和一类(SomeObject::GetTypeInformation()
),它可以为模板,比较有用和重要.
我能想到的唯一方法包括编写两个函数/一个函数和一个常量,每个类,或使用宏.
还有其他方法吗?
Ada*_*eld 73
不,没有办法,因为你打电话会发生什么Object::GetTypeInformation()
?它无法知道要调用哪个派生类版本,因为没有与之关联的对象.
您必须使其成为非静态虚拟功能才能正常工作; 如果您还希望能够在没有对象实例的情况下非虚拟地调用特定派生类的版本,那么您还必须提供第二个冗余静态非虚拟版本.
Ras*_*Kaj 55
许多人说这是不可能的,我会更进一步说它没有意义.
静态成员与任何实例无关,只与该类有关.
虚拟成员不是直接与任何类相关,而是与实例相关.
因此,静态虚拟成员将与任何实例或任何类无关.
Nat*_*C-K 21
前几天我遇到了这个问题:我有一些充满静态方法的类,但我想使用继承和虚方法并减少代码重复.我的解决方案是:
不使用静态方法,而是使用带有虚方法的单例.
换句话说,每个类都应该包含一个静态方法,您可以调用该方法来获取指向该类的单个共享实例的指针.您可以将真正的构造函数设置为private或protected,以便外部代码不会通过创建其他实例来滥用它.
在实践中,使用单例与使用静态方法非常相似,只是您可以利用继承和虚方法.
Als*_*lsk 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(); }
};
Run Code Online (Sandbox Code Playgroud)
如果基类具有大量静态函数并且派生类必须覆盖它们中的每一个并且忘记为虚函数提供重复定义,该怎么办?是的,我们会在运行时遇到一些奇怪的错误,很难追查.导致代码重复是件坏事.以下尝试解决此问题(我想事先告诉它它是完全类型安全的,并且不包含任何黑魔法,如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(); }
};
Run Code Online (Sandbox Code Playgroud)
现在我们可以使用变量"keeper"将对象的类型存储在基类"Object"中:
class Object
{
public:
Object(){}
boost::scoped_ptr<TypeKeeper> keeper;
//not virtual
string getTypeInformation() const
{ return keeper? keeper->getTypeInformation(): string("base class"); }
};
Run Code Online (Sandbox Code Playgroud)
在派生类中,必须在构造期间初始化守护程序:
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"; }
};
Run Code Online (Sandbox Code Playgroud)
让我们添加语法糖:
template<class T>
void override_static_functions(T* t)
{ t->keeper.reset(new TypeKeeperImpl<T>()); }
#define OVERRIDE_STATIC_FUNCTIONS override_static_functions(this)
Run Code Online (Sandbox Code Playgroud)
现在,后代的声明看起来像:
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"; }
};
Run Code Online (Sandbox Code Playgroud)
用法:
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()
Run Code Online (Sandbox Code Playgroud)
好处:
缺点:
开放式问题:
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
Run Code Online (Sandbox Code Playgroud)
2)如何在每个构造函数中隐式调用OVERRIDE_STATIC_FUNCTIONS?
Tim*_*imo 12
虽然Alsk已经给出了非常详细的答案,但我想添加一个替代方案,因为我认为他的增强实现过于复杂.
我们从一个抽象基类开始,它为所有对象类型提供接口:
class Object
{
public:
virtual char* GetClassName() = 0;
};
Run Code Online (Sandbox Code Playgroud)
现在我们需要一个实际的实现.但是为了避免必须同时编写静态和虚方法,我们将使实际的对象类继承虚拟方法.如果基类知道如何访问静态成员函数,这显然只能起作用.所以我们需要使用一个模板并将实际的对象类名传递给它:
template<class ObjectType>
class ObjectImpl : public Object
{
public:
virtual char* GetClassName()
{
return ObjectType::GetClassNameStatic();
}
};
Run Code Online (Sandbox Code Playgroud)
最后,我们需要实现我们的真实对象.这里我们只需要实现静态成员函数,虚拟成员函数将继承自ObjectImpl模板类,用派生类的名称实例化,因此它将访问它的静态成员.
class MyObject : public ObjectImpl<MyObject>
{
public:
static char* GetClassNameStatic()
{
return "MyObject";
}
};
class YourObject : public ObjectImpl<YourObject>
{
public:
static char* GetClassNameStatic()
{
return "YourObject";
}
};
Run Code Online (Sandbox Code Playgroud)
让我们添加一些代码来测试:
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;
}
Run Code Online (Sandbox Code Playgroud)
附录(2019年1月12日):
除了使用GetClassNameStatic()函数,您还可以将类名定义为静态成员,甚至是"内联",IIRC从C++ 11开始工作(不要被所有修饰符吓到:)):
class MyObject : public ObjectImpl<MyObject>
{
public:
// Access this from the template class as `ObjectType::s_ClassName`
static inline const char* const s_ClassName = "MyObject";
// ...
};
Run Code Online (Sandbox Code Playgroud)
Ale*_*tov 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
};
Run Code Online (Sandbox Code Playgroud)
小智 8
不,这是不可能的,因为静态成员函数缺少this
指针.静态成员(函数和变量)本身并不是类成员.它们碰巧被ClassName::member
类访问说明符调用并遵守它们.他们的存储被定义在课外的某个地方; 每次实例化类的对象时都不会创建存储.指向类成员的指针在语义和语法方面都很特殊.指向静态成员的指针是所有方面的普通指针.
类中的虚函数需要this
指针,并且非常耦合到类,因此它们不能是静态的.
这是不可能的,但这只是因为遗漏。这并不像很多人声称的那样“没有意义”。明确地说,我正在谈论这样的事情:
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.
}
Run Code Online (Sandbox Code Playgroud)
这是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();
}
Run Code Online (Sandbox Code Playgroud)
这工作正常,基本上发生的事情是编译器生成两个表,称为 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
};
Run Code Online (Sandbox Code Playgroud)
接下来,每个具有虚函数的类都增加了另一个指向其 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";
}
};
Run Code Online (Sandbox Code Playgroud)
那么当你打电话时实际发生了什么b->sayMyName()
?基本上是这样的:
b->vtable[Base_Virtual_Functions::sayMyName](b);
Run Code Online (Sandbox Code Playgroud)
(第一个参数变为this
。)
好的,那么它如何与静态虚函数一起工作?那么静态和非静态成员函数有什么区别?唯一的区别是后者得到一个this
指针。
我们可以对静态虚函数做完全相同的事情——只需删除this
指针。
b->vtable[Base_Virtual_Functions::sayMyName]();
Run Code Online (Sandbox Code Playgroud)
这可以支持两种语法:
b->sayMyName(); // Prints "Base" or "Derived"...
Base::sayMyName(); // Always prints "Base".
Run Code Online (Sandbox Code Playgroud)
所以忽略所有反对者。它确实有道理。那为什么不支持呢?我认为这是因为它没有什么好处,甚至可能有点混乱。
与普通虚函数相比,唯一的技术优势是您不需要传递this
给函数,但我认为这不会对性能产生任何可测量的差异。
这确实意味着对于有实例和没有实例的情况,您没有单独的静态和非静态函数,但也可能令人困惑的是,当您使用它时,它只是真正的“虚拟”实例调用。
归档时间: |
|
查看次数: |
90546 次 |
最近记录: |