且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

从基类指针访问派生类成员的设计方案

更新时间:2023-02-14 23:28:34

我终于决定使用 boost :: variant 在我的列表 class。我的属性类现在是一个模板类,其中模板参数用 ValueType 类的子类实例化。 属性的不同口味来源于属性类。



现在看来,这种方法似乎满足了我所有的初始要求:




  • 它使界面的复杂度保持不变✓

  • 迭代列表中的所有属性,并调用属性可以使用 std :: for_each boost :: apply_visitor

  • 当具有 Property 的实例时,如果我的属性基类允许访问其 ValueType 成员。 ✓

  • 不需要使用dynamic_cast或类似结构✓



对此方法的任何评论仍然赞赏。


I'm writing a DAL/ORM library. This library will be accessed mainly from GUIs but also from some "business level" applications. I'm still in the design phase of this library and came to a point where I'm not sure how to solve the following issue nicely.

In my current design I have a class, let's call it List for the moment, that has a container of another class, Properties. Properties come in two flavors (A and B), with mostly the same functionality, but some of their functionality is different. Furthermore both flavors of Properties store values. Values can be of different data types, including, but not limited to, PODs. Each List can contain a certain Property only once and Properties are identified by a "name", i.e. a string.

I now want to be able to do all of the following:

  • Keep the complexity of the interface as low as possible
  • Iterate over all Properties in List, and call methods that both Property flavors support.
  • When having an instance of a Property, accessing its value in a type safe way
  • If possible, avoid dynamic_cast or similar constructs

So, obviously pure polymorphism cannot do the trick here. I have done some experiments with the curiously recurring template pattern and with composition of two class hierarchies - one for Properties and one for their values (example code below). However, so far I did not succeed in getting a design that fulfilled all my requirements.

The basic design (i.e. which classes exist, how they are organized etc.) is not fixed and could be easily changed. I am still in the design phase of this project, so only test code exists. However, the basic idea has to bee like explained above (i.e. that a List has Properties which in turn have values).

Any solutions to my problems or raw ideas, thoughts, etc. are highly appreciated.


Example code for hierarchy implementation. Obviously I will not be able to access a property's value in a type-safe way here.

class PropertyValue {
public:
    virtual std::string GetAsString() const = 0;

    bool IsReadOnly() const { return m_isReadOnly; }
    void IsReadOnly(const bool val) { m_isReadOnly = val; }

protected:
    PropertyValue(PropertyValue & other) : m_isReadOnly(other.m_isReadOnly)
    {};

    PropertyValue(bool readOnly) : m_isReadOnly(readOnly)
    {};

private:
    bool m_isReadOnly;
};

class StringValue : public PropertyValue {
private:
    typedef std::string inner_type;
public:
    StringValue(const inner_type & value, bool readOnly) : PropertyValue(readOnly)
                                                         , m_value(value)
    {};

    StringValue(StringValue & other) : PropertyValue(other.IsReadOnly())
                                     , m_value(other.m_value)
    {};

    std::string GetAsString() const { return m_value; };

    inner_type GetValue() const { return m_value; };
    void SetValue(const inner_type & value) { m_value = value; };

    unsigned int GetMaxLenght() const { return m_maxLength; };

private:
    inner_type m_value;
    unsigned int m_maxLength;
};

class IntValue : public PropertyValue {
private:
    typedef int inner_type;
public:
    IntValue(const inner_type & value, bool readOnly) : PropertyValue(readOnly)
                                                      , m_value(value)
    {};

    IntValue(IntValue & other) : PropertyValue(other.IsReadOnly())
                               , m_value(other.m_value)
    {};

    std::string GetAsString() const { char tmp[((CHAR_BIT * sizeof(int)) / 3 + 1)]; return itoa(m_value, tmp, 10); };

    inner_type GetValue() const { return m_value; };
    void SetValue(const inner_type & value) { m_value = value; };

    int GetMinValue() const { return m_minValue; };
    int GetMaxValue() const { return m_maxValue; };

private:
    inner_type m_value;
    int m_minValue;
    int m_maxValue;
};

class Property {
public:
    Property(std::auto_ptr<PropertyValue> value, bool visible)
    {
        m_value = value;
        m_isVisible = visible;
    }

    bool IsVisible() const { return m_isVisible; }
    void IsVisible(const bool val) { m_isVisible = val; }

    std::string GetValueAsString() const { return m_value->GetAsString(); };

    const PropertyValue & getValue() const { return (*m_value.get()); }

private:
    std::auto_ptr<PropertyValue> m_value;
    bool m_isVisible;
};

class PropertyFlavorA : public Property {
public:
    PropertyFlavorA(std::auto_ptr<PropertyValue> value, bool visible) : Property(value, visible)
    {
        value->IsReadOnly(true);
    };
};

class PropertyFlavorB : public Property {
public:
    PropertyFlavorB(std::auto_ptr<PropertyValue> value, bool visible) : Property(value, visible) {};
};

I finally decided to use a vector of boost::variant in my List class. My Property class is now a template class where the template parameter is instantiated with subclasses of a ValueType class. The different flavors of Properties derive from the Property class.

Right now it seems that this approach fulfills all my initial requirements:

  • It keeps the complexity of the interface low ✓
  • Iterating over all Properties in List, and calling methods that both Property flavors support is possible by using std::for_each and boost::apply_visitor
  • When having an instance of a Property, accessing its value in a type safe way is possible if my Property base class gives access to its ValueType member. ✓
  • Use of dynamic_cast or similar constructs is not needed ✓

Any comments on this approach are still appreciated.