【问题标题】:C++ static virtual members?C++ 静态虚拟成员?
【发布时间】:2010-12-21 16:27:21
【问题描述】:

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

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

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

还有其他解决方案吗?

【问题讨论】:

  • 只是旁注:静态方法不会在任何实例上执行,这意味着它们没有隐式 this 指针。话虽如此,方法签名中的const 将隐式this 指针标记为常量,并且不能应用于静态方法,因为它们缺少隐式参数。
  • @cvb:我会认真重新考虑用不涉及反射的代码替换您的示例。现在的方式是将两个单独的(尽管相关的)问题混为一谈。是的,我知道你问这个问题已经 5 年半了。
  • 这里隐含要求的功能之一是让编译器检查层次结构中的每个对象是否实现了特定接口(其中一个或多个方法是静态的)。基本上,静态方法的纯虚拟检查很有意义,因为如果您忘记添加静态方法,那么编译器应该会出错。 virtual 不是这里的关键字,它更像是 abstract ,它恰好是 C++ 中的一种同义词,除了这种特殊情况。不幸的是,目前你不能用 C++ 做到这一点。

标签: c++ static virtual


【解决方案1】:

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

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

【讨论】:

  • 如果您将静态类(或类静态成员)视为单例,那么一切都变得显而易见 - 在您的情况下,只需调用 Object::GetTypeInformation - 与在基础上调用常规虚拟方法的方式相同类实例。 (当然,if C++ 支持虚拟静态方法)
  • 这是一个完全似是而非的论点。如果您使用类而不是对象,它自然会使用该类的版本,而不是进行虚拟调度。没有什么新东西。
【解决方案2】:

很多人说不可能,我更进一步说没有意义。

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

虚拟成员与任何类都没有直接关系,只与实例有关。

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

【讨论】:

  • 这在类是一等值的语言中非常有意义 - 例如。 Delphi 有这个,也有“静态虚拟”方法。
  • 我也认为静态虚拟是有意义的。可以定义接口类并包含必须在派生类中实现的静态方法。
  • 对于static virtual方法意义不大,但static virtual方法在接口中是非常有意义的。
  • 拥有一个static const string MyClassSillyAdditionalName 非常有意义。
  • 如果您打算使用编译器来确保您在所有子类中实现了静态方法,这完全有意义。 @BretKuhns 是对的。
【解决方案3】:

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

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

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

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

【讨论】:

  • 这会降低我的性能 - 除非编译器可以确定:1. 它实际上是一个单例并且 2. 没有继承它,我认为它不能优化所有开销.
  • 如果这种事情的表现让你担心,那么 C# 可能不适合你。
  • 啊,好点子。显然,自从我在 2009 年编写它以来,我已经有一段时间没有考虑过这个问题了。让我换一种说法,那么:如果这种性能问题让你担心,那么也许你应该完全避免使用继承。发帖人特地求虚方法,奇怪你来这里吐槽虚方法的开销。
【解决方案4】:

虽然 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() 函数,您还可以将类名定义为静态成员,甚至是“内联”,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】:

    有可能!

    但究竟有什么可能,让我们缩小范围。人们经常想要某种“静态虚函数”,因为需要重复代码才能通过静态调用“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(); }
    };
    

    如果基类有大量的静态函数,而派生类必须重写它们中的每一个,而忘记为虚函数提供重复定义怎么办。是的,我们会在 runtime 期间遇到一些难以追踪的奇怪错误。因为重复代码是一件坏事。下面尝试解决这个问题(我想提前告诉你它是完全类型安全的,并且不包含任何像 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"); }
    
    };
    

    在派生类中,keeper必须在构造时初始化:

    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 在每个 构造函数)

    缺点:

    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
    

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

    【讨论】:

    • +1 表示努力,尽管我不确定这是否比仅将功能委托给具有虚拟方法的单例更优雅。
    • @einpoklum,我能想到这样的情况更可取。假设我们有很多已经调用静态方法的客户端代码。从静态方法切换到使用虚拟方法的单例需要更改客户端代码,而上述解决方案是非侵入性的。
    • “Foo::getTypeInformation”和“TypeKeeperImpl::getTypeInformation”不需要“virtual”关键字。
    【解决方案6】:

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

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

    【讨论】:

    • 另外,静态方法不能是 const。只是没有意义,他们不会变异什么实例?
    • 这主要是代码重复。这个想法是让子类只需要拥有静态 const 成员,而不必让代码访问它。
    【解决方案7】:

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

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

    【讨论】:

    • 只有非静态函数需要this 指针。静态函数不是特定于实例的,也不需要它。所以 - 这不是虚拟静态成员不可能的原因。
    【解决方案8】:

    这是不可能的,但这只是因为一个疏忽。这并不是很多人似乎声称的“没有意义”的事情。为了清楚起见,我说的是这样的事情:

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

    考虑普通的虚函数是如何工作的。删除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();
    }
    

    这工作正常,基本上发生的事情是编译器创建两个表,称为 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-&gt;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 传递给该函数,但我认为这不会对性能产生任何可衡量的影响。

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

    【讨论】:

      【解决方案9】:

      嗯,这是一个相当晚的答案,但可以使用奇怪的重复模板模式。这篇wikipedia 文章包含您需要的信息,静态多态下的示例也是您需要的。

      【讨论】:

        【解决方案10】:

        我认为您尝试做的事情可以通过模板来完成。我试图在这里阅读字里行间。您要做的是从某些代码中调用一个方法,该方法调用派生版本,但调用者未指定哪个类。示例:

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

        【讨论】:

        • 如果您将代码缩进 4 个空格,您可以将其自动格式化。或者,我相信您可以使用反勾号在内联实现相同的目的。
        • 这是我错过的显而易见的事情。谢谢你。尽管如此,pubic 成员还是很奇怪。
        • M() 不是静态函数。怎么叫 T::M()?
        【解决方案11】:

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

        【讨论】:

        • 只有特定于实例的虚拟方法需要实例的 vtable。你可以有一个静态的——每类一个——vtable。如果你想让实例知道,只需从实例的 vtable 指向类静态 vtable。
        • 然而动态调度并不一定需要传递对象指针(显式或隐式)。他似乎追求的是结合动态调度而不必传递对象指针,这反过来又允许在没有对象的情况下调用方法(在这种情况下,当然会使用静态调度)。实际上没有什么是不可能的——除了 C++ 没有(直接)支持它。
        【解决方案12】:

        这个问题已有十多年的历史了,但看起来它获得了大量的流量,所以我想使用我在其他任何地方都没有见过的现代 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();
        }
        

        【讨论】:

          【解决方案13】:

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

          【讨论】:

          • 自从 C++20 引入 constexpr virtuals 后不再存在
          【解决方案14】:

          首先,答案是正确的,即 OP 请求的内容是矛盾的:虚拟方法取决于实例的运行时类型;静态函数特别不依赖于实例——只依赖于类型。也就是说,让静态函数返回特定于类型的内容是有意义的。例如,我有一系列用于 State 模式的 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 留作练习。
          • 您从静态返回了一个引用。除非你有一个单身人士,否则这是有问题的。

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

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

          【讨论】:

          • 虚拟静态方法,如果它们存在的话,将不依赖于实例中的任何东西——但实例需要知道它的类型才能调用它们。这可以通过编译器来解决(例如,通过使用一些带有指向虚拟静态方法和成员的指针的每个类的单一数据结构。)这在术语上当然不矛盾。
          • 术语是否矛盾是语义问题。可以想象 C++ 允许从实例调用静态(例如,Foo foo; ... foo::bar(); 而不是Foo::bar();)。这与decltype(foo)::bar(); 没有什么不同,但它又是静态绑定的。访问者方法似乎是获得此行为的合理方法,而不仅仅是使静态方法成为虚拟 const 方法。
          【解决方案15】:

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

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

          简单来说,您有一个从自身模板化的类,例如 class myclass :public myancestor。从这一点开始,myancestor 类现在可以调用您的静态 T::YourImpl 函数。

          【讨论】:

            【解决方案16】:

            如果您希望使用 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

            【讨论】:

              【解决方案17】:

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

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

              【讨论】:

              • 是的,我知道,4 年。为那些不想详细阅读代码的人解释-score。 Base::mSelf 指的是任何派生类的最近构造的实例,即使该实例已被破坏。所以class D1 : public Base ...; class D2 : public Base ...; ...; D1* pd1 = new D1(); D2* pd2 = new D2(); pd1-&gt;MyStaticFun(); /* calls D2::MyVirtualFun() */ delete pd2; pd1-&gt;MyStaticFun(); /* calls via deleted pd2 */ 这不是我们想要的。
              【解决方案18】:

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

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

              静态函数在编译时确定。

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

              【讨论】:

              • 从技术上讲,这是未定义的行为。您不能出于任何原因尊重空指针。对空指针唯一可以做的事情是 a) 为它分配另一个指针 b) 将它与另一个指针进行比较。
              • 此外,你只能比较它是否相等(或不等式_与另一个指针,而不是排序。即p &lt; nullp &gt;= null等也都是未定义的。
              • @KeithB -​​ 为了完整起见,您还可以安全地在空指针上调用 delete。
              猜你喜欢
              • 2010-09-07
              • 1970-01-01
              • 2010-11-17
              • 2023-03-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多