【问题标题】:Overriding static variables when subclassing子类化时覆盖静态变量
【发布时间】:2010-10-10 07:58:51
【问题描述】:

我有一个类,我们称它为 A,在该类定义中我有以下内容:

static QPainterPath *path;

也就是说,我声明了一个指向路径对象的静态(类范围)指针;此类的所有实例现在都将具有相同的共享数据成员。我希望能够以此类为基础,将其子类化为更专业的形式,分层行为,并且每个类都有自己独特的路径对象(但不必重复计算边界框或调用绘画例程等无聊的部分)。

如果我将它子类化以创建一个类 F(例如),我希望 F 使用从 A 继承的绘图例程,但要使用在 F 中声明的静态(类范围)路径对象。我尝试过上面的声明在私有部分(并在派生类 F 中重复),并尝试将它放在受保护的部分中,所有这些都没有任何乐趣。

我可以理解为什么会这样:

void A::paint() {
    this->path...

指的是 A::path 而不是 F::path,即使对象属于 F 类。

有没有一种优雅的方法来解决这个问题,并允许每个类维护一个静态路径对象,同时仍然使用基类中定义的绘图代码,并使所有类(可能除了基类)都是真实的和可实例化的?

【问题讨论】:

    标签: c++ inheritance static overriding


    【解决方案1】:

    使用虚方法获取静态变量的引用。

    class Base {
    private:
        static A *a;
    public:
        A* GetA() {
            return a;
        }
    };
    
    class Derived: public Base {
    private:
        static B *b;
    public:
        A* GetA() {
            return b;
        }
    };
    

    请注意,这里的 B 派生自 A。那么:

    void Derived::paint() {
        this->GetA() ...
    }
    

    【讨论】:

    • 确实,感谢您的关注。我也没有提到必须以某种方式初始化静态变量:)
    • 谢谢,这帮助我很好地解决了这个问题。我为静态成员和简单的 getter 函数使用了单独的名称,但由于目的是避免在每个类中使用 derived::paint(),我将 getPath() 设为虚拟,它修复了所有问题。非常感谢。
    • 您说使用虚拟方法,但我在该代码中的任何地方都没有看到关键字virtual。是什么让方法变得虚拟?
    • @abelenky 该方法确实不是虚拟的,尽管它被覆盖了。这样做的结果是,当你有一个B 的对象和一个指向你的B 的A* 类型的指针并调用该方法时,然后调用A 的版本而不是B 版本(当您使用 B* 类型的指针执行此操作时,它会正常工作)。
    【解决方案2】:

    您也许可以在混音或 Curiously recurring template pattern 上做一个变体

    #include <stdio.h>
    
    typedef const char QPainterPath;
    
    class Base
    {
    public:
        virtual void paint() { printf( "test: %s\n", getPath() ); }
        virtual QPainterPath* getPath() = 0;
    };
    
    template <class TYPE>
    class Holder : public Base
    {
    protected:
        static QPainterPath* path;
        virtual QPainterPath* getPath() { return path; }
    };
    
    class Data1 : public Holder<Data1>
    {
    };
    
    class Data2 : public Holder<Data2>
    {
    };
    
    template <> QPainterPath* Holder<Data1>::path = "Data1";
    template <> QPainterPath* Holder<Data2>::path = "Data2";
    
    int main( int argc, char* argv[] )
    {
    Base* data = new Data1;
    data->paint();
    delete data;
    
    data = new Data2;
    data->paint();
    delete data;
    }
    

    我刚刚在 CodeBlocks 中运行了这段代码,得到了以下结果:

    test: Data1
    test: Data2
    
    Process returned 0 (0x0)   execution time : 0.029 s
    Press any key to continue.
    

    【讨论】:

    • 要解决提问者的问题,您还需要将所有绘图例程移到此模板类中。这可能会破坏其他设计要求(例如,具有一个指向 base 的容器并在每个容器上调用 x->do_something() 的典型场景——模板方法不能是虚拟的)。
    • 是的,但现在我意识到这不是 CRTP 模式:在模板类 Holder 中的任何地方都没有使用 TYPE。 Holder 甚至不需要是模板。您所拥有的基本上与其他人给出的“普通虚函数”解决方案相同,但将 2 个成员移入了一个中间类。
    • 我认为我们必须同意在这里不同意,因为它是一个 CRTP,它是一个更好的解决方案,因为如果您忘记为新类型创建新路径,链接器会告诉您。跨度>
    • 对不起,我猜它是 CRTP,我只是习惯于看到它用于“编译时多态性”。是的,如果您忘记定义,链接器会抱怨,这是一件好事。 OTOH,您需要为每个可以派生的类定义一个新的 Holder 样式模板类。
    • 如果你做了这个模板就不会 class Holder : public BASE :)
    【解决方案3】:

    这个我没有测试过,但是引入了一个虚函数:

    struct Base {
    
        void paint() {
             APath * p = getPath();
             // do something with p
        }
    
        virtual APath * getPath() {
             return myPath;
        }
    
        static APath * myPath;
    };
    
    struct Derived : public Base  {
    
        APath * getPath() {
             return myPath;
        }
        static APath * myPath;
    };
    

    可能是你想要的。请注意,您仍然必须在某处定义这两个静态:

    APath * Base::myPath = 0;
    APath * Derived::myPath = 0;
    

    【讨论】:

    • +1。完整而清晰。 (我修正了几个错别字。)虽然你可能会通过在两个类中相同地命名静态变量来引起程序员的少量混淆,但请注意(我必须检查 C++ 是否允许它!)
    • 这应该是答案 - 它更完整并且开箱即用。
    【解决方案4】:

    您可以使用虚函数来实现您的结果。这可能是您最干净的解决方案。

    class A
    {
        protected:
            virtual QPainterPath *path() = 0;
    
        private:
            static QPainterPath *static_path;  /* Lazy initalization? */
    };
    
    QPainterPath *A::path()
    {
        return A::static_path;
    }
    
    class F : public A
    {
        protected:
            virtual QPainterPath *path() = 0;
    
        private:
            static QPainterPath *F_static_path;  /* Lazy initalization? */
    };
    
    QPainterPath *A::path()
    {
        return F::F_static_path;
    }
    

    【讨论】:

    • 您添加“= 0”以使 path() 成为纯虚拟是否有原因? AFAIK 所做的只是禁用 path() 的动态调度——即,当编译时对象的具体类型未知时,它不能被调用——我不明白为什么这是可取的。否则你的解决方案看起来不错!
    • @j_random_hacker,据我所知,这会强制重新实现该功能。函数体是给定的,因此它是可调用的。也许我自己错过了一些东西(不太可能)。
    • @strager: 看来我们都部分正确(部分错误...... :) 根据这个有趣的页面:gotw.ca/gotw/031.htm,添加“=0”确实会强制重新实现派生类,但是它也会阻止当前类被实例化。
    【解决方案5】:

    我知道这个问题已经得到解答,但是还有另一种方法可以通过帮助类和一些模板特化来为多个类设置类似静态变量的值。

    它并没有完全回答这个问题,因为它与子类化没有任何联系,但我遇到了同样的问题,我找到了一个不同的解决方案我想分享。

    例子:

    template <typename T>
    struct Helper {
      static QPainterPath* path;
      static void routine();
    }
    
    // Define default values
    template <typename T> QPainterPath* Helper<T>::path = some_default_value;
    template <typename T> void Helper<T>::routine { do_somehing(); }
    
    class Derived {};
    
    // Define specialized values for Derived
    QPainterPath* Helper<Dervied>::path = some_other_value;
    void Helper<Dervied>::routine { do_somehing_else(); }
    
    int main(int argc, char** argv) {
      QPainterPath* path = Helper<Derived>::path;
      Helper<Derived>::routine();
      return 0;
    }
    

    优点:

    • 干净,编译时初始化
    • 静态访问(无实例化)
    • 您也可以声明专门的静态函数

    缺点:

    • 没有虚拟化,您需要确切的类型来检索信息

    【讨论】:

      【解决方案6】:

      您不能“覆盖”静态函数,更不用说静态成员变量了。

      你需要的可能是一个虚函数。这些只能是实例函数,因此如果没有类实例,它们将无法访问。

      【讨论】:

        【解决方案7】:

        您可能不希望覆盖静态变量。也许你可以在你的类中存储一个指针?

        class A
        {
            public:
                A() :
                    path(static_path)
                {
                }
        
            protected:
                A(QPainterPath *path)
                    : path(path)
                {
                }
        
            private:
                QPainterPath *path;
        
                static QPainterPath *static_path;  /* Lazy initalization? */
        };
        
        class F : public A
        {
            public:
                F() :
                    A(F_static_path)
                {
                }
        
            private:
                static QPainterPath *F_static_path;  /* Lazy initalization? */
        };
        

        【讨论】:

          【解决方案8】:

          如果您不关心外观,只需在使用路径之前使用 A:: 或 F:: 来选择正确的路径,或者如果您不喜欢 :: 以不同的方式命名它们。

          另一种选择是使用函数来整理它,例如虚拟 QPainterPath* GetPath() { return A::path; } 在 A 和 QPainterPath* GetPath() { return F::path; } 在 F 中。

          虽然这个问题只是关于代码的外观而不是它的作用,而且由于它并没有真正改变可读性,我不会为此烦恼...

          【讨论】:

          • 我认为它确实很重要,因为您想在一个不同的指针上重用继承的绘图例程。您不能通过在绘图例程中添加“A::”或“F::”来做到这一点,因为他们不知道他们需要哪种类型!这里需要虚函数。
          猜你喜欢
          • 2016-03-03
          • 2014-07-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-07-29
          • 2013-10-27
          • 1970-01-01
          相关资源
          最近更新 更多