c++静态虚拟成员?

在c++中是否有可能同时具有staticvirtual的成员函数?显然,没有一种直接的方法可以做到这一点(static virtual member();是一个编译错误),但至少有一种方法可以达到同样的效果吗?

即:

1
2
3
4
5
6
7
8
9
10
11
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()是有意义的,这对于比较很有用,对于模板也很重要。

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

其他的解决方案吗?


不,没有办法这样做,因为当您调用Object::GetTypeInformation()时会发生什么?它不知道调用哪个派生类版本,因为没有与之关联的对象。

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


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

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

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

所以静态虚拟成员是与任何实例或类无关的。


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

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

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

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


这是可能的!

但究竟什么是可能的,让我们缩小范围。人们经常需要某种"静态虚函数",因为通过静态调用"SomeDerivedClass::myfunction()"和多态调用"base_class_pointer->myfunction()"来调用同一个函数,需要重复代码。允许这种功能的"合法"方法是复制函数定义:

1
2
3
4
5
6
7
8
9
10
11
12
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。然后,为了能够通过指向基类的指针访问派生类的静态函数,我们必须以某种方式在基类中存储对象的类型。一种方法是使用"奇怪地重复出现的模板模式"使基类模板化,但是这里不合适,我们将使用一种称为"类型擦除"的技术:

1
2
3
4
5
6
7
8
9
10
11
class TypeKeeper
{
public:
    virtual string getTypeInformation() = 0;
};
template<class T>
class TypeKeeperImpl: public TypeKeeper
{
public:
    virtual string getTypeInformation() { return T::getTypeInformationStatic(); }
};

现在我们可以在基类"object"中存储对象的类型,并使用变量"keeper":

1
2
3
4
5
6
7
8
9
10
11
class Object
{
public:
    Object(){}
    boost::scoped_ptr<TypeKeeper> keeper;

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

};

在派生类中,必须在构造过程中初始化看守:

1
2
3
4
5
6
7
8
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"; }
};

让我们添加语法糖:

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

现在,后代的声明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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"; }
};

用法:

1
2
3
4
5
6
7
8
9
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
2
3
4
5
6
7
8
9
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已经给出了一个非常详细的答案,但是我想添加一个替代方案,因为我认为他的增强实现过于复杂。

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

1
2
3
4
5
class Object
{
public:
    virtual char* GetClassName() = 0;
};

现在我们需要一个实际的实现。但是为了避免同时编写静态方法和虚拟方法,我们将让实际对象类继承虚拟方法。这显然只在基类知道如何访问静态成员函数的情况下才有效。所以我们需要使用一个模板,并将实际的对象类名传递给它:

1
2
3
4
5
6
7
8
9
template<class ObjectType>
class ObjectImpl : public Object
{
public:
    virtual char* GetClassName()
    {
        return ObjectType::GetClassNameStatic();
    }
};

最后,我们需要实现真正的对象。这里我们只需要实现静态成员函数,虚拟成员函数将从ObjectImpl模板类继承,并用派生类的名称实例化,因此它将访问它的静态成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyObject : public ObjectImpl<MyObject>
{
public:
    static char* GetClassNameStatic()
    {
        return"MyObject";
    }
};

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

让我们添加一些代码到测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
char* GetObjectClassName(Object* object)
{
    return object->GetClassName();
}

int main()
{
    MyObject myObject;
    YourObject yourObject;

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

    return 0;
}

增编(2019年1月12日):

除了使用GetClassNameStatic()函数,您还可以将类名定义为静态成员,甚至是"内联"成员,IIRC从c++ 11开始就这样工作了(不要被所有的修饰符:):

1
2
3
4
5
6
7
8
class MyObject : public ObjectImpl<MyObject>
{
public:
    // Access this from the template class as `ObjectType::s_ClassName`
    static inline const char* const s_ClassName ="MyObject";

    // ...
};

这是可能的。创建两个函数:静态函数和虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct Object{    
  struct TypeInformation;
  static  const TypeInformation &amp;GetTypeInformationStatic() const
  {
      return GetTypeInformationMain1();
  }
  virtual const TypeInformation &amp;GetTypeInformation() const
  {
      return GetTypeInformationMain1();
  }
protected:
  static const TypeInformation &amp;GetTypeInformationMain1(); // Main function
};

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


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

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


好吧,这是一个相当晚的答案,但是使用奇怪地重复出现的模板模式是可能的。这篇wikipedia文章提供了您需要的信息,静态多态下的示例也是您需要的。


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


我认为你想做的可以通过模板来实现。我想从字里行间体会一下。您要做的是从一些代码中调用一个方法,在这些代码中,它调用派生版本,但是调用者没有指定哪个类。例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Foo {
public:
    void M() {...}
};

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

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

int main()
{
    Try();
}

您希望尝试()在不指定Bar的情况下调用Bar版本的M。实现静态的方法是使用模板。所以把它改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Foo {
public:
    void M() {...}
};

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

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

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


使用c++,您可以使用crt方法的静态继承。例如,它在窗口模板atl和amp上得到了广泛的应用。wtl。

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

简单地说,您有一个自定义模板化的类,比如类myclass: public my祖宗。从这一点开始,my始祖类现在可以调用静态T::YourImpl函数。


首先,回答是正确的,即OP所请求的是矛盾的:虚拟方法依赖于实例的运行时类型;静态函数不依赖于实例——只依赖于类型。也就是说,让静态函数返回特定于类型的内容是有意义的。例如,我有一个用于状态模式的MouseTool类家族,我开始让每个类都有一个静态函数,返回相应的键盘修饰符;我在factory函数中使用了这些静态函数,它们生成了正确的MouseTool实例。该函数根据MouseToolA::keyboardModifier()、MouseToolB::keyboardModifier()等检查鼠标状态,然后实例化适当的一个。当然,稍后我想检查状态是否正确,所以我想编写类似"if (keyboardModifier == dynamic_type(*state)::keyboardModifier()"这样的语句(不是真正的c++语法),这就是这个问题所问的。

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

参见:http://en.wikipedia.org/wiki/Visitor_pattern了解背景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct ObjectVisitor;

struct Object
{
     struct TypeInformation;

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

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

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

那么对于每个具体对象:

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

然后定义基本访问者:

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

然后选择合适静态函数的具体访问者:

1
2
3
4
5
6
7
8
9
10
struct ObjectVisitorGetTypeInfo {
    Object::TypeInformation result;
    virtual void visit(const SomeObject&amp; o) {
        result = SomeObject::GetTypeInformation();
    }
    virtual void visit(const AnotherObject&amp; o) {
        result = AnotherObject::GetTypeInformation();
    }
    // Again, an implementation for each concrete Object.
};

最后,使用它:

1
2
3
4
5
void printInfo(Object&amp; o) {
    ObjectVisitorGetTypeInfo getTypeInfo;
    Object::TypeInformation info = o.accept(getTypeInfo).result;
    std::cout << info << std::endl;
}

注:

康斯坦斯作为练习离开了。您从静态返回了一个引用。除非你有一个单例,否则这是值得怀疑的。

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

1
2
3
4
5
6
7
8
9
10
11
12
struct ObjectVisitorGetTypeInfo {
    Object::TypeInformation result;
    virtual void visit(const SomeObject&amp; o) { doVisit(o); }
    virtual void visit(const AnotherObject&amp; o) { doVisit(o); }
    // Again, an implementation for each concrete Object.

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


这是不可能的,但那只是因为遗漏。这并不是像很多人声称的那样"毫无意义"。需要说明的是,我是这样说的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Base {
  static virtual void sayMyName() {
    cout <<"Base";
  }
};

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

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

这是100%可以实现的(只是还没有实现),我认为这是有用的。

考虑正常的虚函数是如何工作的。删除static,并添加一些其他的东西,我们有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Base {
  virtual void sayMyName() {
    cout <<"Base";
  }
  virtual void foo() {
  }
  int somedata;
};

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

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

这样做很好,基本上编译器会生成两个表,称为VTables,并像这样为虚函数分配索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum Base_Virtual_Functions {
  sayMyName = 0;
  foo = 1;
};

using VTable = void*[];

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

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

接下来,每个带有虚函数的类都被另一个指向其VTable的字段扩充,所以编译器基本上将它们修改为这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Base {
  VTable* vtable;
  virtual void sayMyName() {
    cout <<"Base";
  }
  virtual void foo() {
  }
  int somedata;
};

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

那么当您调用b->sayMyName()时会发生什么呢?基本上是这样的:

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

(第一个参数变成this。)

好的,那么它是如何处理静态虚函数的呢?静态成员函数和非静态成员函数有什么区别?唯一的区别是后者获得一个this指针。

我们可以对静态虚函数做完全相同的操作—只需删除this指针。

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

这样就可以同时支持两种语法:

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

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

与普通虚拟函数相比,惟一的技术优势是您不需要将this传递给函数,但是我不认为这会对性能造成任何可度量的差异。

它确实意味着当你有一个实例时,你没有一个单独的静态和非静态函数,当你没有一个实例时,但也可能令人困惑,当你使用实例调用时,它只是真正的"虚拟"。


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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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指针指向虚表(或thunk)用于查找要调用哪个运行时方法的结构。

静态函数在编译时确定。

我在c++静态成员类中展示了这个代码示例;它表明你可以调用一个静态方法给定一个空指针:

1
2
3
4
5
6
7
8
9
10
11
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;
}