C++静态虚拟成员?

cvb*_*cvb 133 c++ virtual static

在C++中是否有可能拥有一个staticvirtual?的成员函数?显然,没有一种直接的方法(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()?它无法知道要调用哪个派生类版本,因为没有与之关联的对象.

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

  • 这是一个完全似是而非的论点.如果你使用类而不是对象,它自然会使用该类的版本,而不是虚拟调度.那里什么新鲜事. (12认同)
  • 如果您将静态类(或类静态成员)视为单例,则一切都变得明显 - 在您的情况下,应该调用Object :: GetTypeInformation - 与在基类*instance*上调用常规虚方法的方式相同.(当然,*如果*C++支持虚拟静态方法) (8认同)

Ras*_*Kaj 55

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

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

虚拟成员不是直接与任何类相关,而是与实例相关.

因此,静态虚拟成员将与任何实例或任何类无关.

  • 在类是一流值的语言中它是完全有意义的 - 例如Delphi具有它,并且还具有"静态虚拟"方法. (31认同)
  • 对于`static virtual`方法来说,它没那么有意义,但是`static`**pure**`virtual`方法在接口中非常有意义. (30认同)
  • 我也认为静态虚拟是有意义的.可以定义接口类并包含必须在派生类中实现的静态方法. (6认同)
  • 究竟."虚函数"(根据定义)是**动态链接的函数**,即它在运行时根据给定对象的**动态类型**选择.因此,没有对象=没有虚拟呼叫. (4认同)
  • 拥有一个`static const string MyClassSillyAdditionalName`是完全有意义的. (4认同)
  • 如果您打算使用编译器来确保在所有子类中实现静态方法,那么这是完全有意义的.@BretKuhns是对的. (3认同)
  • 不是真的 - 记住在这些系统中,类本身就是一种类型的值.最好的情况是,它们的类型(类)来自基本的"basetype",它们的兄弟类型可以是联合,内置函数等.虚拟方法在单个类型的所有实例之间共享; 因此,与"类"类型相关联的虚拟方法将在所有类之间共享. (2认同)
  • 当您需要从父构造函数调用子函数时,它是有意义的。http://stackoverflow.com/q/36807653/239247 (2认同)
  • 它对于静态类工厂函数有意义,如果构造函数失败则返回 null,如果构造函数成功则返回 object。这使我们能够从地球表面清除异常,因为无效对象永远不会直接返回给调用者,而是在两者之间进行检查。构造函数不能失败,因此类必须抛出异常以将问题转移给程序员,而不是让静态方法以无失败的方式包装该异常,或者更优雅的 Promiselike/callback shell。 (2认同)

Nat*_*C-K 21

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

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

换句话说,每个类都应该包含一个静态方法,您可以调用该方法来获取指向该类的单个共享实例的指针.您可以将真正的构造函数设置为private或protected,以便外部代码不会通过创建其他实例来滥用它.

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

  • 好吧,这个问题不是关于C#的,而是:-) (3认同)
  • 啊,好点.显然自从我在2009年写这篇文章以来我已经考虑了一段时间了.让我用另一种方式,然后:如果这种表现的事情让你担心,那么也许你应该完全避免使用继承.海报特别要求虚拟方法,所以你来这里抱怨虚拟方法的开销是很奇怪的. (3认同)

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. 减少重复的代码(但我们必须在每个构造函数中调用OVERRIDE_STATIC_FUNCTIONS)

缺点:

  1. 每个构造函数中的OVERRIDE_STATIC_FUNCTIONS
  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
Run Code Online (Sandbox Code Playgroud)

2)如何在每个构造函数中隐式调用OVERRIDE_STATIC_FUNCTIONS?

  • @einpoklum,我可以想到一种更可取的情况。假设我们有很多已经调用静态方法的客户端代码。从静态方法切换到具有虚拟方法的单例需要更改客户端代码,而上述解决方案是非侵入性的。 (2认同)

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)

  • 另外,静态方法不能是const.它只是没有意义,它们不会变异的实例是什么? (4认同)
  • 这主要是代码重复。这个想法是子类只需要有静态 const 成员,而不需要有代码访问它。 (2认同)

小智 8

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

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


Tim*_*mmm 7

这是不可能的,但这只是因为遗漏。这并不像很多人声称的那样“没有意义”。明确地说,我正在谈论这样的事情:

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%的东西,可以实现(只是还没有),我要说的东西是有用的。

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

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给函数,但我认为这不会对性能产生任何可测量的差异。

这确实意味着对于有实例和没有实例的情况,您没有单独的静态和非静态函数,但也可能令人困惑的是,当您使用它时,它只是真正的“虚拟”实例调用。


tro*_*ana 6

嗯,这是一个很晚的答案,但有可能使用奇怪的重复模板模式.这篇维基百科文章提供了您需要的信息,静态多态性下的示例也是您的要求.