静态虚拟成员 C + + ? ?

有没有可能在 C + + 中有一个既是 static又是 virtual的成员函数?显然,没有一种简单的方法可以做到这一点(static virtual member();是一个编译错误) ,但是至少有一种方法可以达到同样的效果吗?

即:

struct Object
{
struct TypeInformation;


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


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

在实例(object->GetTypeInformation())和类(SomeObject::GetTypeInformation())上都使用 GetTypeInformation()是有意义的,GetTypeInformation()对于比较和模板都很重要。

我能想到的唯一方法包括编写两个函数/一个函数和一个常量,每个类或使用宏。

还有别的办法吗?

142601 次浏览

不,没有办法,因为当你打电话给 Object::GetTypeInformation()的时候会发生什么?它无法知道调用哪个派生类版本,因为没有与之关联的对象。

您必须使它成为一个非静态的虚函数才能正常工作; 如果您还希望能够在没有对象实例的情况下非虚拟地调用特定派生类的版本,那么您还必须提供第二个冗余的静态非虚拟版本。

不,这是不可能的,因为静态成员函数缺少 this指针。静态成员(函数和变量)本身并不是真正的类成员。它们只是碰巧由 ClassName::member调用,并且遵循类访问说明符。它们的存储是在类之外的某个地方定义的; 不会在每次实例化类的对象时创建存储。指向类成员的指针在语义和语法上是特殊的。指向静态成员的指针在所有方面都是普通指针。

类中的虚函数需要 this指针,并且与类非常耦合,因此它们不能是静态的。

不,这是不可能的,因为静态成员是在编译时绑定的,而虚拟成员是在运行时绑定的。

前几天我遇到了这个问题: 我有一些类,里面全是静态方法,但是我想使用继承和虚方法,减少代码重复。我的解决办法是:

不要使用静态方法,而是使用带有虚方法的单例模式。

换句话说,每个类都应该包含一个静态方法,您可以调用该方法来获取指向该类的单个共享实例的指针。您可以将真正的构造函数设置为私有或受保护,这样外部代码就不会通过创建其他实例来误用它。

在实践中,使用单例与使用静态方法非常相似,只是可以利用继承和虚方法。

正如其他人所说,有两条重要信息:

  1. 在进行静态函数调用时没有 this指针
  2. this指针指向虚拟表(或 thunk)用于查找要调用的运行时方法的结构。

静态函数在编译时确定。

我在 类中的 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;
}

许多人说这是不可能的,我会更进一步说,这是没有意义的。

静态成员与任何实例无关,只与类有关。

虚拟成员不直接与任何类相关,只与实例相关。

因此,静态虚拟成员将是与任何实例或任何类都不相关的东西。

这是可能的。使两个函数: 静态和虚拟

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
};

我认为你正在尝试做的事情可以通过模板来完成。我在试着理解你的言外之意。您试图做的是从一些代码调用一个方法,在这些代码中,它调用一个派生版本,但是调用者没有指定哪个类。例如:

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


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


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


int main()
{
Try();
}

您希望 Try()在不指定 Bar 的情况下调用 Bar 版本的 M。处理静态的方法是使用模板。所以像这样改变它:

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


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


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


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

这是可能的!

但是让我们缩小范围,看看到底什么是可能的。人们经常需要某种“静态虚函数”,因为需要重复编写代码,以便能够通过静态调用“ Some  衍生类: : myfunction ()”和多态调用“ base _ class _ point-> 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 ()是虚拟的,就不可能调用“ Some  衍生类: : 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(); }
};

现在我们可以将对象的类型存储在基类“ Object”中,并使用一个变量“ keep”:

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. 减少代码的复制(但我们 必须打电话 中覆盖 _ STATIC _ 函数 建造者)

缺点:

  1. 中覆盖 _ STATIC _ 函数 构造函数
  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 _ 函数?

也许你可以试试我的解决方案:

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;
}

这个回答有点晚不过用奇异递归模板模式也可以。这篇 维基百科文章提供了您需要的信息,静态多态性下的示例也是您需要的。

首先,回复是正确的,OP 请求的是一个矛盾的术语: 虚方法依赖于一个实例的运行时类型; 静态函数特别地不依赖于一个实例——只依赖于一个类型。也就是说,让静态函数返回特定于类型的内容是有意义的。例如,我有一系列用于 State 模式的 MouseTool 类,我开始让每个类都有一个静态函数,返回与之相关的键盘修饰符; 我在工厂函数中使用了这些静态函数,生成了正确的 MouseTool 实例。该函数根据 MouseToolA: : keyboardModfier ()、 MouseToolB: : keyboardModfier ()等检查鼠标状态,然后实例化适当的状态。当然,后来我想检查状态是否正确,所以我想写一些类似于“ if (keyboardModfier = = Dynamic _ type (* state) : : keyboardModfier ())”(不是真正的 C + + 语法)的内容,这就是这个问题要问的。

因此,如果你发现自己想要这样做,你可能需要重新考虑你的解决方案。尽管如此,我理解拥有静态方法,然后根据实例的动态类型动态调用它们的愿望。我认为 访客模式可以给你你想要的。它能给你你想要的。这是一些额外的代码,但对其他访问者可能很有用。

参见: 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;
};

然后对于每一个具体的目标:

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;
}

备注:

  • 坚持是一种锻炼。
  • 您从静态中返回了一个引用。除非您有一个单例,否则这是值得怀疑的。

如果你想避免复制粘贴错误,你的其中一个访问方法调用了错误的静态函数,你可以使用一个模板化的 helper 函数(它本身不能是虚拟的)来访问你的访问者,模板如下:

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();
}
};

虽然阿尔斯克已经给出了一个相当详细的答案,但我还想添加一个替代方案,因为我认为他的增强实现过于复杂。

我们从一个抽象基类开始,它为所有对象类型提供接口:

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;
}

增编(二零一九年一月十二日) :

与使用 GetClassNamestatic ()函数不同,您还可以将类名定义为一个静态成员,甚至是“ inline”,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";


// ...
};

不,静态成员函数不能是虚拟的。由于虚拟概念是在运行时通过 vptr 解析的,而 vptr 是 class 的非静态成员,因为静态成员函数不能访问 vptr,所以静态成员不能是虚拟的。

这是不可能的,但那只是因为一个疏忽。这并不像很多人说的那样“毫无意义”。先说清楚,我说的是这样的:

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,添加一些其他的东西,我们有:

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".

所以别理会那些反对者。是的说得通。那么为什么不支持它呢?我认为这是因为它几乎没有什么好处,甚至可能有点令人困惑。

与普通虚拟函数相比,唯一的技术优势是不需要将 this传递给函数,但我认为这不会对性能产生任何可衡量的影响。

它确实意味着当你有一个实例或者没有一个实例时,你没有一个单独的静态和非静态函数,但是当你使用实例调用时,它只是真正的“虚拟”,这可能会让人感到困惑。

使用 c + + ,您可以使用 crt 方法的静态继承。例如,它被广泛应用于 atl & wtl 窗口模板。

参见 https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

简单地说,您拥有一个类,它是从类 myclass: public myfather 这样的类本身模板化的。从这一点开始,我的祖先类现在可以调用静态的 T: : YourImpl 函数。

这个问题已经有十多年的历史了,但是看起来它获得了不错的流量,所以我想发布一个使用现代 C + + 特性的替代方案,这是我在其他任何地方都没有看到过的。

该解决方案使用 CRTP 和 SFINAE 进行静态调度。这本身并不是什么新鲜事,但我发现所有这类实现都缺乏针对“重写”的严格签名检查此实现要求“重写”方法签名与“重写”方法的签名完全匹配。这种行为更接近于虚函数的行为,同时也允许我们有效地重载和“覆盖”静态方法。

请注意,我把覆盖放在引号中,因为严格来说,我们没有在技术上覆盖任何东西。相反,我们使用签名 Y 调用一个分派方法 X,该方法将其所有参数转发给 T: : X,其中 T 是类型列表中的第一个类型,这样 T: : X 使用签名 Y 存在。考虑用于分派的类型列表可以是任何类型,但通常包括默认实现类和派生类。

实施

#include <experimental/type_traits>


template <template <class...> class Op, class... Types>
struct dispatcher;


template <template <class...> class Op, class T>
struct dispatcher<Op, T> : std::experimental::detected_t<Op, T> {};


template <template <class...> class Op, class T, class... Types>
struct dispatcher<Op, T, Types...>
: std::experimental::detected_or_t<
typename dispatcher<Op, Types...>::type, Op, T> {};




// Helper to convert a signature to a function pointer
template <class Signature> struct function_ptr;


template <class R, class... Args> struct function_ptr<R(Args...)> {
using type = R (*)(Args...);
};




// Macro to simplify creation of the dispatcher
// NOTE: This macro isn't smart enough to handle creating an overloaded
//       dispatcher because both dispatchers will try to use the same
//       integral_constant type alias name. If you want to overload, do it
//       manually or make a smarter macro that can somehow put the signature in
//       the integral_constant type alias name.
#define virtual_static_method(name, signature, ...)                            \
template <class VSM_T>                                                     \
using vsm_##name##_type = std::integral_constant<                          \
function_ptr<signature>::type, &VSM_T::name>;                          \
\
template <class... VSM_Args>                                               \
static auto name(VSM_Args&&... args)                                       \
{                                                                          \
return dispatcher<vsm_##name##_type, __VA_ARGS__>::value(              \
std::forward<VSM_Args>(args)...);                                  \
}

示例用法

#include <iostream>


template <class T>
struct Base {
// Define the default implementations
struct defaults {
static std::string alpha() { return "Base::alpha"; };
static std::string bravo(int) { return "Base::bravo"; }
};


// Create the dispatchers
virtual_static_method(alpha, std::string(void), T, defaults);
virtual_static_method(bravo, std::string(int), T, defaults);
    

static void where_are_the_turtles() {
std::cout << alpha() << std::endl;  // Derived::alpha
std::cout << bravo(1) << std::endl; // Base::bravo
}
};


struct Derived : Base<Derived> {
// Overrides Base::alpha
static std::string alpha(){ return "Derived::alpha"; }


// Does not override Base::bravo because signatures differ (even though
// int is implicitly convertible to bool)
static std::string bravo(bool){ return "Derived::bravo"; }
};


int main() {
Derived::where_are_the_turtles();
}

如果您希望使用 virtual static是为了能够在类的静态部分上定义一个接口,那么使用 C + + 20 concept就可以解决您的问题。

class ExBase { //object properties
public: virtual int do(int) = 0;
};


template <typename T> //type properties
concept ExReq = std::derived_from<T, ExBase> && requires(int i) { //~constexpr bool
{
T::do_static(i) //checks that this compiles
} -> std::same_as<int> //checks the expression type is int
};


class ExImpl : virtual public ExBase { //satisfies ExReq
public: int do(int i) override {return i;} //overrides do in ExBase
public: static int do_static(int i) {return i;} //satisfies ExReq
};


//...


void some_func(ExReq auto o) {o.do(0); decltype(o)::do_static(0);}

(这种方法对会员也同样适用!)

了解更多关于概念如何工作的信息: https://en.cppreference.com/w/cpp/language/constraints

对于 C + + 20中添加的标准概念: https://en.cppreference.com/w/cpp/concepts

我浏览了其他答案,似乎没有一个提到虚函数表(vtable) ,这就解释了为什么 为什么是不可能的。

C + + 类中的静态函数编译成与常规命名空间中的其他函数实际上相同的函数。

换句话说,当您声明一个函数 static时,您使用的是类名作为名称空间,而不是一个对象(它有一个实例和一些相关数据)。

让我们快速看看这个..。

// This example is the same as the example below
class ExampleClass
{
static void exampleFunction();


int someData;
};


// This example is the same as the example above
namespace ExampleClass
{
void exampleFunction();


// Doesn't work quite the same. Each instance of a class
// has independent data. Here the data is global.
int someData;
}

在了解了 static成员函数实际上是什么之后,我们现在可以考虑 vtables 了。

如果在类中声明任何虚函数,则编译器将创建一个数据块,该数据块(通常)位于其他数据成员之前。这个数据块包含运行时信息,它告诉程序在运行时它需要跳转到内存中的哪个位置,以便为可能在运行时创建的类的每个实例执行正确的(虚)函数。

这里的重点是“数据块”。为了使该数据块存在,它必须作为对象(类)实例的一部分存储。如果函数是 static,那么我们已经说过它使用类的名称作为名称空间。没有与该函数调用关联的对象。

更详细的说明: 静态函数没有隐式的 this指针,它指向对象所在的内存。因为它没有这个,所以您不能跳到内存中的某个位置并找到该对象的 vtable。所以不能执行虚函数调度。

无论如何,我都不是编译器工程方面的专家,但至少在这个层面上理解事物是有帮助的,而且(希望如此?)这使得我们很容易理解为什么(至少在 C + + 中) static virtual没有意义,而且编译器也不能把它翻译成合理的内容。