C ++静态虚拟成员?

时间:2009-11-30 15:26:47

标签: c++ static virtual

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

即:

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()是有意义的,这对于比较非常有用,对模板至关重要。

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

还有其他解决方案吗?

17 个答案:

答案 0 :(得分:72)

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

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

答案 1 :(得分:55)

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

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

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

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

答案 2 :(得分:21)

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

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

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

实际上,使用单例非常类似于使用静态方法,除了可以利用继承和虚方法。

答案 3 :(得分:14)

有可能!

但究竟有什么可能,让我们缩小范围。人们经常需要某种“静态虚函数”,因为能够通过静态调用“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()定义,很明显它必须是 static 函数的定义,因为无法调用“SomeDerivedClass: :getTypeInformation()“如果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()

优点:

  1. 减少重复的代码(但我们 不得不打电话 每个人都有OVERRIDE_STATIC_FUNCTIONS个 构造函数)
  2. 缺点:

      每个人都有
    1. OVERRIDE_STATIC_FUNCTIONS 构造
    2. 记忆和表现     开销
    3. 复杂性增加
    4. 未解决的问题:

      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?

答案 4 :(得分:12)

虽然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()函数之外,您还可以将类名定义为静态成员,甚至是#34; inline&#34;,这是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";

    // ...
};

答案 5 :(得分:10)

有可能。制作两个功能:静态和虚拟

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

答案 6 :(得分:8)

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

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

答案 7 :(得分:6)

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

答案 8 :(得分:3)

不,静态成员函数不能是虚拟的。因为虚拟概念是在运行时借助vptr解析的,而vptr是class.due的非静态成员,静态成员函数可以&#39; t acess vptr所以静态成员不能是虚拟的。

答案 9 :(得分:2)

我认为您尝试做的事情可以通过模板完成。我想在这里读一下这些。你要做的是从一些代码调用一个方法,它调用派生版本,但调用者没有指定哪个类。例如:

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

答案 10 :(得分:1)

这个问题已经有十多年的历史了,但看起来它的流量很大,所以我想发布一个使用现代 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();
}

答案 11 :(得分:0)

首先,回复是正确的,OP请求的是一个矛盾:虚方法取决于实例的运行时类型;静态函数特别不依赖于实例 - 只是在一个类型上。也就是说,让静态函数返回特定于类型的东西是有意义的。例如,我有一个用于状态模式的MouseTool类,我开始让每个人都有一个静态函数返回随之而来的键盘修饰符;我在工厂函数中使用了那些生成正确MouseTool实例的静态函数。该函数针对MouseToolA :: keyboardModifier(),MouseToolB :: keyboardModifier()等检查鼠标状态,然后实例化相应的一个。当然后来我想检查状态是否正确所以我想写一些像“if(keyboardModifier == dynamic_type(* state):: keyboardModifier())”(不是真正的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;
}

注意:

  • Constness留作练习。
  • 您从静态返回了引用。除非你有一个单身人士,否则这是值得怀疑的。

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

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

答案 12 :(得分:0)

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

答案 13 :(得分:0)

这是不可能的,但这仅仅是因为遗漏了。许多人似乎声称这不是“没有意义的”事情。明确地说,我正在谈论这样的事情:

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传递给该函数,但我认为这不会对性能产生任何可衡量的影响。

这确实意味着在拥有实例和没有实例的情况下,您没有单独的静态和非静态函数,但这也可能使您感到困惑,因为它只是真正的“虚拟”当您使用实例调用时。

答案 14 :(得分:0)

使用c ++,可以将静态继承与crt方法一起使用。例如,它广泛用于窗口模板atl和wtl。

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

为简单起见,您有一个从其本身进行模板化的类,例如myclass类:public myancestor。从这一点开始,myancestor类现在可以调用您的静态T :: YourImpl函数。

答案 15 :(得分:-1)

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

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

答案 16 :(得分:-3)

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

  1. 在进行静态函数调用和
  2. 时没有this指针
  3. this指针指向使用虚拟表或thunk查找要调用的运行时方法的结构。
  4. 静态函数在编译时确定。

    我在C++ static members in class中展示了此代码示例;它表明你可以在给定空指针的情况下调用静态方法:

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