【问题标题】:Overwrite Base Class Member with New Type用新类型覆盖基类成员
【发布时间】:2014-01-02 06:05:42
【问题描述】:

我正在尝试使用 C++ 来模拟动态类型之类的东西。我正在处理继承类的问题。例如,一个函数可以定义为

BaseClass* myFunction(int what) {
    if (what == 1) {
        return new DerivedClass1();
    } else if (what == 2) {
        return new DerivedClass2();
    }
}

基类和每个派生类将具有相同的成员,但类型不同。例如,BaseClass 可能有 int xyz = 0(什么都没有),DerivedClass1 可能有 double xyz = 123.456DerivedClass2 可能有 bool xyz = true。然后,我可以创建返回一种类型但实际上返回几种不同类型的函数。问题是,当我尝试这样做时,我总是访问基类的xyz 版本。我尝试过使用指针(void* 用于基类,而“正确”用于派生类),但是每次我想访问成员时,我都必须执行类似 *(double*)(obj->xyz) 的操作,这最终会变得非常杂乱无章。

下面是我的代码大纲:

#include <iostream>

using std::cout;
using std::endl;

class Foo {
public:
    Foo() {};

    void* member;
};

class Bar : public Foo {

public:
    Bar() {
        member = new double(123.456); // Make member a double
    };

};

int main(int argc, char* args[]) {
    Foo* obj = new Bar;

    cout << *(double*)(obj->member);

    return 0;
};

我想我想问的是,这是“好的”编码实践吗?如果没有,对于返回多种类型或接受多种类型的函数是否有不同的方法?

【问题讨论】:

  • 我更喜欢使用模板,而不是void*
  • 请扩展...?我应该以什么方式使用模板?
  • 在基类中,声明了member
  • 然后从不同的模板中派生出派生类?这不会使它们成为不同的类型(我相信 C++ 将 BaseClass&lt;int&gt; 视为与 BaseClass&lt;double&gt; 无关的类)?
  • 是的,结果就是这样。但我认为你也可以只提供一个模板化的成员函数,它至少包装来自void* 的演员表(可能还有一些静态检查),而不是让整个类成为模板。

标签: c++ derived-class return-type base-class


【解决方案1】:

你可以试试下面的设计:

#include <iostream>

using std::cout;
using std::endl;

template<typename T>
class Foo {
public:
    Foo() {};
    virtual T& member() = 0;
};

class Bar : public Foo<double> {

public:
    Bar() : member_(123.456) {
    };

    virtual double& member() { return member_; }    
private:
    double member_;
};

int main(int argc, char* args[]) {
    Foo<double>* obj = new Bar;

    cout << obj->member();

    return 0;
};

但因此,Foo 类已经需要专门化,并且不再是 any 类型的容器。
这样做的其他方法是,例如在基类中使用boost::any

【讨论】:

    【解决方案2】:

    如果您需要动态解决方案,您应该坚持使用 void* 和 size 或 boost::any。您还需要将一些类型信息作为整数代码或字符串传递,以便您可以解码内容的实际类型。 另请参阅属性设计模式。 例如,您可以查看 zeromq 套接字选项https://github.com/zeromq/libzmq/blob/master/src/options.cpp

    【讨论】:

    • 属性设计模式?你有一个不那么通用的名字吗?谷歌搜索不显示任何内容。
    【解决方案3】:

    这实际上不是这样做的。

    有两种典型的方法可以在 C++ 中实现类似于动态类型的功能:

    • 面向对象的方式:类层次结构和Visitor 模式
    • 函数式编程方式:标记联合

    后者使用boost::variant 相当简单,前者在网络上有很好的记录。我个人会推荐boost::variant 开始。

    如果您想走完整的动态打字之路,那么事情就会变得更加棘手。在动态类型中,对象通常表示为包含其他对象和函数的字典,函数接受对象列表/字典并返回对象列表/字典。用C++建模是可行的,但是会很啰嗦……


    如何用动态类型语言表示对象?

    更通用的表示是语言将对象表示为一组值(通常命名)和一组方法(也命名)。简化表示如下:

    struct Object {
        using ObjectPtr = std::shared_ptr<Object>;
        using ObjectList = std::vector<ObjectPtr>;
        using Method = std::function<ObjectList(ObjectList const&)>;
    
        std::map<std::string, ObjectPtr> values;
        std::map<std::string, Method> methods;
    };
    

    如果我们以 Python 为例,我们意识到我们缺少一些东西:

    1. 例如,我们无法实现getattr,因为ObjectPtrMethod 是不同的类型
    2. 这是一个递归实现,但没有基础:我们缺少固有类型(通常是BoolIntegerString、...)

    处理第一个问题相对容易,我们将对象转换为能够可调用

    class Object {
    public:
        using ObjectPtr = std::shared_ptr<Object>;
        using ObjectList = std::vector<ObjectPtr>;
        using Method = std::function<ObjectList(ObjectList const&)>;
    
        virtual ~Object() {}
    
        //
        // Attributes
        //
        virtual bool hasattr(std::string const& name) {
            throw std::runtime_error("hasattr not implemented");
        }
    
        virtual ObjectPtr getattr(std::string const&) {
            throw std::runtime_error("gettattr not implemented");
        }
    
        virtual void setattr(std::string const&, ObjectPtr) {
            throw std::runtime_error("settattr not implemented");
        }
    
        //
        // Callable
        //
        virtual ObjectList call(ObjectList const&) {
            throw std::runtime_error("call not implemented");
        }
    
        virtual void setcall(Method) {
            throw std::runtime_error("setcall not implemented");
        }
    }; // class Object
    
    class GenericObject: public Object {
    public:
        //
        // Attributes
        //
        virtual bool hasattr(std::string const& name) override {
            return values.count(name) > 0;
        }
    
        virtual ObjectPtr getattr(std::string const& name) override {
            auto const it = values.find(name);
            if (it == values.end) {
                throw std::runtime_error("Unknown attribute");
            }
    
            return it->second;
        }
    
        virtual void setattr(std::string const& name, ObjectPtr object) override {
            values[name] = std::move(object);
        }
    
        //
        // Callable
        //
        virtual ObjectList call(ObjectList const& arguments) override {
            if (not method) { throw std::runtime_error("call not implemented"); }
            return method(arguments);
        }
    
        virtual void setcall(Method m) {
            method = std::move(m);
        }
    private:
        std::map<std::string, ObjectPtr> values;
        Method method;
    }; // class GenericObject
    

    而处理第二个问题需要播种递归:

    class BoolObject final: public Object {
    public:
        static BoolObject const True = BoolObject{true};
        static BoolObject const False = BoolObject{false};
    
        bool value;
    }; // class BoolObject
    
    class IntegerObject final: public Object {
    public:
        int value;
    }; // class IntegerObject
    
    class StringObject final: public Object {
    public:
        std::string value;
    }; // class StringObject
    

    现在您需要添加一些功能,例如值比较。

    【讨论】:

    • 由于不能选择使用 Boost,您能否详细说明您是如何谈论字典/列表的?字典或列表不能只保存一种类型的对象吗?这让我回到了最初的问题......
    • 优秀的答案。非常感谢!
    • 我包含了必要的头文件,但是当我编译时,我得到了这个:pastebin.com/tRHihqkE(使用 GCC。Clang 给出了类似的错误)
    • @CalebP:我在这里使用 C++11 表示法,在 C++98 中,您将改为使用 typedef std::shared_ptr&lt;Object&gt; ObjectPtr;。如果我没记错的话,你可以使用 -std=c++11 标志激活 C++11。
    • 使用哪个编译器? Clang 还是 GCC?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多