views:

1783

answers:

7

I have a class, lets call it A, and within that class definition I have the following:

static QPainterPath *path;

Which is to say, I'm declaring a static (class-wide) pointer to a path object; all instances of this class will now have the same shared data member. I would like to be able to build upon this class, subclassing it into more specialised forms, layering behaviour, and with each class having its own unique path object (but not having to repeat the boring bits like calculating bounding boxes or calling the painting routines).

If I subclass it to create a class F (for example), I want F to use the inherited drawing routines from A, but to use the static (class-wide) path object declared in F. I have tried having the declaration above in the private section (and repeating it in the derived class F), and tried having it in the protected section, all with no joy.

I can sort of see why this is happening:

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

is referring to A::path instead of F::path, even when the object is of class F.

Is there an elegant way to get round this, and allow each class to maintain a static path object, while still using drawing code defined in the base class, and having all classes (except perhaps the base class) be real and instantiatable?

A: 

You can't "override" static functions, let alone static member variables.

What you need is probably a virtual function. These can only be instance functions, so they will not be accessible without class instance.

Anton Gogolev
A: 

You probably don't want static variables to the overriden. Maybe you can store a pointer in your class instead?

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? */
};
strager
+4  A: 

Use a virtual method to get a reference to the static variable.

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

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

Notice that B derives from A here. Then:

void Derived::paint() {
    this->GetA() ...
}
João da Silva
Syntax for your static lines looks wrong.
strager
Indeed, thanks for noticing. I also didn't mention that the static variables have to be initialized somehow :)
João da Silva
Thanks, this helped me solve the problem nicely. I went with separate names for the static members, and the simple getter function, but since the aim is to avoid having derived::paint() in each class, I made the getPath() virtual, which fixed everything. Many thanks.
A: 

You can use virtual functions to achieve your result. This is probably your cleanest solution.

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;
}
strager
Is there a reason why you added " = 0" to make path() pure virtual? AFAIK all that does is disable dynamic dispatch for path() -- i.e. it can't be called when the concrete type of the object is unknown at compile time -- and I can't see why that is desirable. Otherwise your solution looks good!
j_random_hacker
@j_random_hacker, From what I know, this forces the function to be reimplemented. The function body is given, thus it is callable. Maybe I'm missing something myself (not unlikely).
strager
@strager: Well it seems we're both partly right (and partly wrong... :) According to this interesting page: http://www.gotw.ca/gotw/031.htm, adding "=0" does force reimplementation in a derived class, however it also prevents the current class from being instantiated.
j_random_hacker
+1  A: 

I haven't tested this, but introducing a virtual function:

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

may be what you want. Note you still have to define the two statics somewhere:

APath * Base::myPath = 0;
APath * Derived::myPath = 0;
anon
+1. Complete and clear. (I fixed a couple of typos.) Though you might cause a small amount of programmer confusion by naming the static variable identically in both classes mind you (I had to check whether C++ allows it!)
j_random_hacker
A: 

If you don't care about the appearance just use A:: or F:: preceding the use of path to choose the correct one, or if you don't like :: name them differently.

Another option is to use a function to tidy this away, e.g. virtual QPainterPath* GetPath() { return A::path; } in A and QPainterPath* GetPath() { return F::path; } in F.

Really though this issue is just about how the code looks rather than what it does, and since it doesn't really alter readability I wouldn't fret about this...

jheriko
I think it *does* matter, because you want to reuse the inherited drawing routines on a *different* pointer. You can't do this by adding "A::" or "F::" to the drawing routines as they don't know which type they need! Virtual functions are necessary here.
j_random_hacker
+1  A: 

You might be able to do a variant on a mix in or 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;
}

I have just run this code in CodeBlocks and got the following:

test: Data1
test: Data2

Process returned 0 (0x0)   execution time : 0.029 s
Press any key to continue.
David Allan Finch
To solve the asker's problem, you would also need to move all drawing routines into this template class. This might break other design requirements (e.g. the typical scenario of having a container of pointers to base and invoking x->do_something() on each -- template methods can't be virtual).
j_random_hacker
I have code that does the above that works.
David Allan Finch
Yes it does, but now I realise it's not the CRTP pattern: TYPE is not used anywhere inside template class Holder. Holder doesn't even need to be a template. What you have is basically the same "plain virtual function" solution given by others, but with the 2 members moved into an intermediate class.
j_random_hacker
I think we will have to agree to disagree here as it is a CRTP and it is a better solution as the linker will tell you if you forgot get create a new path for a new type.
David Allan Finch
Sorry I guess it is the CRTP, I'm just used to seeing that used for "compile-time polymorphism" instead. Yes, the linker will complain if you forget the definition, which is a good thing. OTOH you need to define a new Holder-style template class for each class that can be derived from.
j_random_hacker
Not if you did this template <class TYPE,class BASE> class Holder : public BASE:)
David Allan Finch
True. But here comes my next complaint :) I think that the problem this approach guards against (namely, forgetting to add those 2 members to a derived class) is about as likely as accidentally deriving directly from Base rather than via a Holder template (which has the same effect).
j_random_hacker
+1 for making me think harder about this approach and the CRTP in general :)
j_random_hacker
As Base::getPath() is abstract you will get a compilation error if you derive directly from Base. Thank you very much for the +1.
David Allan Finch