LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Access inherited member from base class? (C++) (https://www.linuxquestions.org/questions/programming-9/access-inherited-member-from-base-class-c-832117/)

cbh2000 09-14-2010 12:05 PM

Access inherited member from base class? (C++)
 
I have a problem. This class is a base class for a basic enum handler:

Code:

#define ENUMOBJECT_BEGIN_ELEMENTS enum Element {
#define ENUMOBJECT_END_ELEMENTS ElementCount, DefaultElement, NoElement };

struct EnumObject
{
    ENUMOBJECT_BEGIN_ELEMENTS
        /* Just default elements defined by macro... */
    ENUMOBJECT_END_ELEMENTS

    EnumObject();
    ...
    EnumObject& operator =(Element e);
    ...

private:
    Element __ELEMENT__;
};

This is a base class that is meant to be inherited, like this:

Code:

class Enum : public EnumObject
{
public:

    ENUMOBJECT_BEGIN_ELEMENTS
        EnumElement1,
        EnumElement2,
        ...,
    ENUMOBJECT_END_ELEMENTS
};

However, when I run into code like this...
Code:

Enum e;
e = Enum::EnumElement1;

...obviously there is a problem, since the inherited member function "EnumObject& operator =(Element e);" is from "EnumObject" and does not recognize "Enum" members (this makes sense).
However, is there a way that I could use a base class to modify an inherited class's data?

cbh2000 09-14-2010 01:07 PM

Band-Aid Solution
 
I came up with a solution:
Code:

#define ENUMOBJECT_BEGIN(objectName) \
    struct objectName \
    { \
        enum Element

#define ENUMOBJECT_END(objectName) ; \
        \
        objectName(Element e = No ## objectName) { __ELEMENT__ = e; } \
        virtual ~objectName() {} \
        \
        operator Element() { return __ELEMENT__; } \
        operator int() { return __ELEMENT__; } \
        objectName& operator =(Element e) { __ELEMENT__ = e; return *this; } \
        \
        bool isValid() const { return __ELEMENT__ < objectName ## Count; } \
        \
    private: \
        Element __ELEMENT__; \
    };

#define ENUMOBJECT_DEFAULT(objectName, defaultValue) \
    objectName ## Count, No ## objectName, Default ## objectName = defaultValue

ENUMOBJECT_BEGIN(ErrorCode)
{
    Normal = 0,
    GeneralError = 1,

    ENUMOBJECT_DEFAULT(ErrorCode, GeneralError)
}
ENUMOBJECT_END(ErrorCode)

int main()
{
    ErrorCode e = ErrorCode::DefaultErrorCode;
    return e; // returns 1
}

It is not very flexible, but it is simple...

Sergei Steshenko 09-14-2010 01:36 PM

The thread name itself suggests you are trying to do a wrong thing. I.e. base classes are not supposed to know their children. I.e. your overall design/architecture appear to be wrong.

dugan 09-14-2010 02:03 PM

Quote:

Originally Posted by cbh2000 (Post 4097292)
Is there a way that I could use a base class to modify an inherited class's data?

This sentence describes polymorphism.

Code:

class Base
{
public:
    int x;
    virtual void initialize();
};

void Base::initialize()
{
    x = 1;
}

class Derived: public Base
{
public:
    int y;
    void initialize();
};

void Derived::initialize()
{
    Base::initialize();
    y = 1;
}

Base *base = new Derived();
ptr->initialize();
delete base;

Presto. The base class has modified the inherited class' data.

johnsfine 09-14-2010 02:42 PM

I hate the excessive use of #define in your first version. But the far more excessive abuse of #define in the second version is far worse.

When you want to do something like that with #define, you can almost always do something better with templates.

Quote:

Originally Posted by cbh2000 (Post 4097292)
However, is there a way that I could use a base class to modify an inherited class's data?

The most powerful way to do that is for the base class to be a template which takes the derived class as its template parameter.

When you first see this, it will look impossibly circular. But C++ allows it and C++ compilers handle it correctly and it is a very powerful method to inject common functionality into multiple classses even when that functionality must be customized to each class.

To make this easier for you to understand, I'll leave in the ugly #defines of your first version.

I'm not taking the time to test this, so there might be a few details wrong. I've used this approach many times, so the approach is valid even if some details are wrong

Code:

#define ENUMOBJECT_BEGIN_ELEMENTS enum Element {
#define ENUMOBJECT_END_ELEMENTS ElementCount, DefaultElement, NoElement };

template <class Actual>
struct EnumObject
{
    typedef typename Actual::Element Element;
    Actual& cast() {return *static_cast<Actual*>(this);}
    Actual& operator =(Element e)
    {
        __ELEMENT__ = e;
        return cast();
    }
    ...

private:
    Element __ELEMENT__;
};

class Enum : public EnumObject<Enum>
{
public:

    ENUMOBJECT_BEGIN_ELEMENTS
        EnumElement1,
        EnumElement2,
        ...,
    ENUMOBJECT_END_ELEMENTS
};


cbh2000 09-16-2010 12:37 AM

Indeed! I had just come to the same solution myself, and was about to go to bed after congratulating myself but stumbled upon your reply while checking my email. :)

Code:

template<typename Inherited>
struct EnumBase
{
    enum Element
    {
        Count,
        Default,
        None
    };

    EnumBase(Inherited::Element e = None);

private:
    Element e;
};

However, I really like your idea of using the base's functions as the inherited's. Thank you!

Quote:

I hate the excessive use of #define in your first version. But the far more excessive abuse of #define in the second version is far worse.
Aww! I've been fond of macros lately! ;)

Maybe I need to read my "Big C++" book, again...

johnsfine 09-16-2010 08:19 AM

Quote:

Originally Posted by cbh2000 (Post 4099012)
I had just come to the same solution myself

A few questions about your version:

Is there some purpose to defining enum Element{...} in the base class? So far as I understand, you never want to use that version of Element. I think my approach:
typedef typename Actual::Element Element;
is much more effective, because it makes Element in the base class be the actual type of Element in the actual class.

With your version of Element in the base class, what is the purpose of
private: Element e;
That Element is the wrong type to use for values of Actual::Element

What compiler are you using? I don't think the following line of your code is valid:
EnumBase(Inherited::Element e = None);
Older compilers will recognize that Inherited::Element is a type name and be able to parse that, but the C++ standard says they shouldn't, so newer compilers should not be able to parse that. Also, you have made None the wrong type, so that shouldn't compiler either.

But I am impressed you thought of the main idea of inheriting from a base class that is templated by the inherited class. Had you seen that trick before? I would never have started using that myself if I hadn't seen it in other code. I would have assumed it was too circular to compile.

cbh2000 09-16-2010 10:16 AM

Actually, yes, I do want to use the base class' Element enum, because only then may I provide two default elements: "None" (Unassigned) and "Default" without having to provide them in the inherited class.

I didn't compile the code when I put it here (I encountered the problems you have mentioned and fixed them when trying to compile), and I'm using GCC 4.5. And, you are right, it won't compile:
Code:

../untitled/enumobject.h: In instantiation of ‘EnumObject<Enum>’:
../untitled/enumobject.h:20:1:  instantiated from here
../untitled/enumobject.h:7:38: error: invalid use of incomplete type ‘class Enum’
../untitled/enumobject.h:19:7: error: forward declaration of ‘class Enum’

Ugh!

I'm becoming frustrated with C++'s lack of ingenuity... Is there a way to force the compiler to define Enum later? (extern keyword only works on variables or functions.)

I'm tempted to create my own preprocessor, but it wouldn't be "standards compliant."

cbh2000 09-16-2010 11:06 AM

Forget it. I'm just doing this from now on:
Code:

class Argument
{
public:
    enum ID
    {
        Help,
        ...
        IDCount,
        NoID,
        DefaultID = NoID
    };

    enum Type
    {
        Option,
        ...
        TypeCount,
        NoType,
        DefaultType = Parameter
    };

    ...
};

int main()
{
    Argument help(Argument::Help, Argument::Option, ...);
    ...
    return 0;
}

I was hoping for something like this...
Code:

int main()
{
    Argument help(Argument::ID::Help, Argument::Type::Option, ...);
    ...
    return 0;
}

...but it is easiest to use macros to achieve the affect, as in this code:
Code:

class Argument
{
public:
    ENUM(ID)
        Help,
        ...
    DEFAULT_ENUM(ID, NoID)

    ENUM(Type)
        Option,
        ...
    DEFAULT_ENUM(Type,  Parameter)
    ...
};

Which expands to:
Code:

class Argument
{
public:
    struct ID
    {
        enum Element
        {
            Help,
            ...
            IDCount,
            NoID,
            DefaultID = NoID
        };

        ID(Element e = NoID) { this->e = e; }

        operator Element() { return e; }
        operator int() { return e; }
        ID& operator =(Element e) { this->e = e; return *this; }

        bool isValid() const { return e < IDCount; }

    private:
        Element e;
    };

    struct Type
    {
        enum Element
        {
            Option,
            ...
            TypeCount,
            NoType,
            DefaultType = Parameter
        };

        Type(Element e = NoType) { this->e = e; }

        operator Element() { return e; }
        operator int() { return e; }
        Type& operator =(Element e) { this->e = e; return *this; }

        bool isValid() const { return e < TypeCount; }

    private:
        Element e;
    };
    ...
};

...which is not as efficient as simply using an enum.


All times are GMT -5. The time now is 09:57 AM.