当子类化时覆盖静态变量

20

我有一个类,我们称之为A,在该类定义内部,我有以下代码:

static QPainterPath *path;
换句话说,我声明了一个指向路径对象的静态(全类范围)指针;所有此类的实例现在将具有相同的共享数据成员。我希望能够构建这个类,并将其继承为更专业的形式,分层行为,并且每个类都有自己独特的路径对象(但不必重复计算边界框或调用绘画例程等乏味工作)。
如果我创建一个名为F的子类,我希望F使用从A继承的绘图例程,但使用在F中声明的静态(类范围)路径对象。我尝试将声明放在私有部分(并在派生类F中重复它),并尝试将其放在受保护的部分,但是没有成功。
我可以有点理解为什么会出现这种情况:
void A::paint() {
    this->path...

即使对象属于类F,它仍然引用A::path而不是F::path。是否有一种优雅的方式可以解决这个问题,允许每个类维护一个静态路径对象,同时仍然使用在基类中定义的绘图代码,并且使所有类(除了基类)都是真实可实例化的?

8个回答

22

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

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

确实,谢谢你注意到了。我也没有提到静态变量必须以某种方式初始化 :) - Joao da Silva
1
谢谢,这帮助我很好地解决了问题。我为静态成员选择了不同的名称和简单的getter函数,但由于目标是避免在每个类中都有derived::paint(),所以我将getPath()设置为虚函数,这解决了所有问题。非常感谢。 - Tyr
7
你说要使用虚方法,但我在那段代码中没看到关键字“virtual”。是什么使这个方法成为虚方法? - abelenky
@abelenky 这个方法确实不是虚函数,尽管它被重写了。这样做的后果是,当你有一个 B 对象和一个指向你的 BA* 类型指针,并调用该方法时,则会调用 A 版本而不是 B 版本(如果你使用 B* 类型指针进行此操作,则正常工作)。 - Jupiter
这种方法的缺点是,在通用情况下,必须为层次结构中的每个派生类定义一个新的 GetA 方法。 理想情况下,应该设计一种方法,只需要在基类中进行此类定义。 - sancho.s ReinstateMonicaCellio

12

你可能可以使用混合或奇异递归模板模式的变体

#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.

是的,确实如此,但现在我意识到这不是CRTP模式:TYPE在模板类Holder内部没有被使用。Holder甚至不需要成为一个模板。你所拥有的基本上是其他人提供的相同的“普通虚函数”解决方案,但是将这2个成员移动到了一个中间类中。 - j_random_hacker
我认为我们必须同意在这个问题上保持分歧,因为它是一种CRTP,这是一种更好的解决方案,因为链接器将会告诉你是否忘记为新类型创建一个新路径。 - David Allan Finch
抱歉,我猜这可能是CRTP,我只是习惯于将其用于“编译时多态”,而不是其他。是的,如果您忘记定义,链接器会发出警告,这是一件好事。另一方面,您需要为每个可以派生的类定义一个新的Holder样式模板类。 - j_random_hacker
+1 让我更深入地思考这种方法和一般的 CRTP :) - j_random_hacker
由于Base :: getPath()是抽象的,如果您直接从Base派生,将会出现编译错误。非常感谢您的+1。 - David Allan Finch
显示剩余3条评论

8
我还没有测试过,但是介绍一种虚函数的方法:
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;

完整而清晰。 (我修正了几个拼写错误。)不过,通过在两个类中相同命名静态变量,您可能会导致一些程序员的困惑(我必须检查C ++是否允许!) - j_random_hacker
这应该是答案 - 它更完整并且可以直接使用。 - ivan-k

3
您可以使用虚函数来实现您的目标。这可能是最干净的解决方案。
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()成为纯虚函数?据我所知,这只是禁用了path()的动态分派——即在编译时无法确定对象的具体类型时无法调用它——我看不出这有什么好处。否则,你的解决方案看起来很不错! - j_random_hacker
@j_random_hacker,据我所知,这会强制重新实现该函数。给出了函数体,因此它是可调用的。也许我自己也错过了什么(不太可能)。 - strager
@strager:嗯,看起来我们两个都有部分正确(还有部分错误... :) 根据这个有趣的页面:http://www.gotw.ca/gotw/031.htm,添加“=0”确实会强制在派生类中重新实现,但同时也阻止了当前类的实例化。 - j_random_hacker

2

我知道这个问题已经有了答案,但是通过使用帮助类和一些模板特化,还有另外一种为多个类设置类似静态变量值的方法。

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

示例:

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

优点:

  • 清晰,编译时初始化
  • 静态访问(无需实例化)
  • 您还可以声明专门的静态函数

缺点:

  • 没有虚拟化,需要精确类型才能检索信息

0

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

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? */
};

0

你无法“覆盖”静态函数,更不用说静态成员变量了。

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


0

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

另一个选项是使用函数来整理这个问题,例如在A中使用virtual QPainterPath* GetPath() { return A::path; },在F中使用QPainterPath* GetPath() { return F::path; }。

实际上,这个问题只涉及代码的外观而不是功能,而且由于它并没有真正改变可读性,所以不必担心这个问题...


我认为这很重要,因为您想在不同的指针上重用继承的绘图例程。通过将“A ::”或“F ::”添加到绘图例程中,您无法做到这一点,因为它们不知道需要哪种类型!虚函数在这里是必要的。 - j_random_hacker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接