如何在C ++中创建正确的对象层次结构

时间:2010-07-05 09:11:48

标签: c++ oop

我正在构建包含基本类型的对象层次结构,例如整数,布尔值,浮点数等,以及容器类型,如矢量,地图和集合。我正在尝试(能够)构建任意对象层次结构,并且能够轻松地设置/获取它们的值。此层次结构将传递给另一个类(此处未提及),并将从此表示创建一个接口。这是这个层次结构的目的,能够从这些对象创建GUI表示。更确切地说,我有这样的东西:

class ValObject 
{
    public:
        virtual ~ValObject() {}
};

class Int : public ValObject 
{
    public:
        Int(int v) : val(v) {}
        void set_int(int v) { val = v);
        int get_int() const { return val; } 
    private:
        int val; 
};

// other classes for floats, booleans, strings, etc
// ...

class Map : public ValObject {}
{
    public:
         void set_val_for_key(const string& key, ValObject* val);
         ValObject* val_for_key(const string& key);
    private:
         map<string, ValObject*> keyvals;
};

// classes for other containers (vector and set) ... 

客户端应该能够创建和任意对象层次结构,轻松设置和获取它们的值,作为初级程序员,我应该学习如何正确地创建类似这样的类。

我面临的主要问题是如何通过指向基类ValObject的指针设置/获取值。起初,我想我可以在基类中创建许多函数,例如set_intget_intset_stringget_stringset_value_for_key,{{ 1}}等,使它们只适用于正确的类型。但是,我会有很多情况,其中函数什么也不做,只是污染了我的界面。我的第二个想法是创建各种代理对象来设置和获取各种值,例如

get_value_for_key

然后,客户端可以使用此代理通过ValObject设置和获取Int的值:

class ValObject
{
    public:
         virtual ~ValObject() {}    
         virtual IntProxy* create_int_proxy(); // <-- my proxy
};

class Int : public ValObject
{
    public:
         Int (int v) : val(v) {}
         IntProxy* create_int_proxy() { return new IntProxy(&val); }
    private:
         int val;
};

class String : public ValObject
{
    public:
         String(const string& s) : val(s) {}
         IntProxy* create_int_proxy() { return 0; }
  private:
         string val;
};

但是使用这种设计,我仍然有太多的类来在各种子类中声明和实现。这是正确的方法吗?还有其他选择吗?

谢谢。

3 个答案:

答案 0 :(得分:3)

查看boost::anyboost::variant了解现有解决方案。最接近您建议的是boost::any,即使您想为学习目的构建自己的解决方案,代码也很简单,可以阅读和理解 - 如果您需要代码,请不要重新发明轮子,使用boost::any

答案 1 :(得分:1)

C ++的优点之一是这些侵入式解决方案通常不是必需的,但不幸的是,我们仍然看到今天正在实施类似的解决方案。这可能是由于Java,.NET和QT的普及,这些模型遵循这些模型,其中我们有一个通用对象基类,几乎所有东西都继承了它。

通过侵入性,意味着必须修改所使用的类型以使用聚合系统(在这种情况下从基础对象继承)。侵入式解决方案的问题之一(尽管有时是合适的)是它们需要将这些类型与用于聚合它们的系统耦合:类型依赖于系统。对于POD,不可能直接使用侵入式解决方案,因为我们无法更改int的接口,例如:包装器变得必要。对于控制范围之外的类型(如标准C ++库或boost)也是如此。结果是,当在C ++中轻松生成这样的包装器时,您最终会花费大量时间和精力手动创建各种事物的包装器。如果即使在不必要的情况下也会统一应用侵入式解决方案并导致运行时/内存开销,那么对代码也会非常悲观。

使用C ++,您可以轻松获得大量非侵入式解决方案,但是当我们知道可以使用模板将静态多态性与使用虚函数的动态多态性相结合时,尤其如此。基本上,只有在需要此解决方案的情况下,我们才能生成这些带有虚函数的基础对象派生包装器,而不会对不需要这种情况的情况感到悲观。

正如已经建议的那样,boost :: any对于你想要实现的目标来说是一个很好的模型。如果你可以直接使用它,你应该使用它。如果你不能(例如:如果你提供SDK并且不能依赖第三方来获得匹配版本的boost),那么请将解决方案看作一个工作示例。

boost :: any的基本思想是做类似于你正在做的事情,只有这些包装器在编译时生成。如果要在boost :: any中存储int,该类将生成一个int包装类,该类继承自基础对象,该基础对象提供在运行时使任何工作所需的虚拟接口。

  

我面临的主要问题是如何   通过指针设置/获取值   到基类ValObject。首先,   我以为我可以创造很多   基类中的函数,比如   set_int,get_int,set_string,   get_string,set_value_for_key,   get_value_for_key等,并制作它们   仅适用于正确的类型。但   那么,我会有很多案例   功能什么都不做,只是污染   我的界面。

正如您已经正确推断的那样,这通常是一种劣质设计。如果你有很多不适用于你的子类的基函数,那么继承被正确使用的一个标志性的标志是。

考虑I / O流的设计。我们没有像output_int,output_float,output_foo等函数那样的ostream作为ostream中的直接方法。相反,我们可以重载operator&lt;&lt;以非侵入方式输出我们想要的任何数据类型。您的基本类型可以实现类似的解决方案。是否要将小部件与自定义类型相关联(例如:自定义属性编辑器)?我们可以允许:

shared_ptr<Widget> create_widget(const shared_ptr<int>& val);
shared_ptr<Widget> create_widget(const shared_ptr<float>& val);
shared_ptr<Widget> create_widget(const shared_ptr<Foo>& val);
// etc.

您想要序列化这些对象吗?我们可以使用像I / O流这样的解决方案。如果你正在调整你自己的解决方案,比如boost :: any,它可以期望这样的辅助函数已经存在并存储了类型(生成的包装类中的虚函数可以调用create_widget(T),例如

如果你不能这么一般,那么提供一些识别存储类型的方法(类型ID,例如),并根据这种类型ID在客户端代码中适当地处理各种类型的获取/设置。通过这种方式,客户端可以查看存储的内容并相应地处理它的设置/获取值。

无论如何,这取决于你,但确实考虑采用非侵入性的方法,因为它通常不会有问题,而且更灵活。

答案 2 :(得分:0)

使用dynamic_cast来强制层次结构。您不需要为此提供显式接口 - 任何合理的C ++程序员都可以这样做。如果他们不能这样做,你可以尝试枚举不同的类型并为每个类型创建一个整数常量,然后你可以提供一个虚函数来返回,然后你可以static_cast up。

最后,您可以考虑以双调度方式传递函数对象。这具有明确的封装优势。

struct functor {
    void operator()(Int& integral) {
        ...
    }
    void operator()(Bool& boo) {
        ...
    }
};
template<typename Functor> void PerformOperationByFunctor(Functor func) {
    if (Int* ptr = dynamic_cast<Int*>(this)) {
        func(*ptr);
    }
    // Repeat
}

最后,您应该避免创建基本上已经涵盖过的类型。例如,提供64位整数类型和32位整数类型并没有什么意义......它只是不值得麻烦。与double和float相同。