创建一个返回具有现有类继承关系的不同类型的函数

时间:2019-05-07 18:24:18

标签: c++

因此,存在以下现有类:

class Data {};

class String_data : public Data {
  string m_data;
    public:
  string str() { return m_data; }
};

class Integer_data : public Data {
  int m_data;
    public:
  int value() { return m_data; }
};

我正在尝试为基类做类似重载函数的事情:

string get_data(String_data *data) {
  return data->str();
}

int get_data(Integer_data *data) {
  return data->value();
}

问题是这些对象作为基类的对象指针存储在容器中,例如:

std::map<string, Data*> data_list;
data_list.emplace( std::pair<string, Data*>("first", new String_data()) );
data_list.emplace( std::pair<string, Data*>("second", new Integer_data()) );

因此,我正在尝试使其工作如下:

string first_data = get_value(data_list["first"]);
int second_data = get_value(data_list["second"]);

编辑:对不起,这将需要很长时间。我想简单地回答这个问题,但是除非我声明完整的意图,否则它不会起作用。

data_list中的某些项目非常稳定,我想创建一种简单的方法来扩展项目列表,而不必创建包装类,并且对每个项目都有单独的访问方法,如下所示:

class DataListWrapper {
  map<string, Data*> m_data_list;
public:
  string get_house() { return ((String_data*)m_data_list["house"])->str(); }
  int get_loan() { return ((Integer_data*)m_data_list["loan"])->value(); }
  // etc...
};

为确保这些开发人员不会意外输入我创建的全局常数供编译器在编译时检查的字符串键。

#ifndef STRING_CONSTANTS
#define STRING_CONSTANTS
constexpr char c_house[] = "house";
constexpr char c_loan[] = "loan";
#endif

因此,目前我们这样做:

string house = ((String_data*)m_data_list["house"])->str();
int loan = ((Integer_data*)m_data_list["loan"])->value();
call_me(house, loan);

但是我想通过简单,轻松地执行以下操作来扩大列表并轻松获得价值:

string house = get_value(m_data_list[c_house]);
int loan = get_value(m_data_list[c_loan]);
call_me(house, loan)

编辑:从某种意义上说,我想在不进行类型转换的情况下执行此操作,因为它变得非常冗长,并且使人们远离代码在尝试进行所有类型转换时试图做的事情。

1 个答案:

答案 0 :(得分:5)

您可以使用std::variant轻松地实现这一点,它会保留大量元数据,以告知使用哪种类型。因此,您可以删除空的基类:

using Data = std::variant<String_data, Integer_data>;

std::map<string, Data> data_list;

data_list.emplace("first", String_data{});
data_list.emplace("second", Integer_data{});

然后使用访问者来检查基础数据:

auto get_data(String_data const& data) -> std::string {
    return data.str();
}

auto get_data(Integer_data const& data) -> int {
    return data.value();
}

std::visit(
    [](auto& data) { auto value = get_data(data); },
    data_list["first"]
);

std::visit(
    [](auto& data) { auto value = get_data(data); },
    data_list["second"]
);

但是,如果要保留类层次结构,则始终可以使用虚拟多态性来实现自己的访问者:

struct Visitor {
    virtual void operator()(int const&) const = 0;
    virtual void operator()(std::string const&) const = 0;
};

struct PrintVisitor : Visitor {
    void operator()(int const& value) const override {
        std::cout << value;
    }

    void operator()(std::string const& value) const override {
        std::cout << value;
    }
};

struct Data {
    virtual void visit(Visitor const&) const = 0;
};

struct String_data : Data {
    void visit(Visitor const& v) const override {
        v(m_data);
    }

private:
    std::string m_data;
};

struct Integer_data : Data {
    void visit(Visitor const& v) const override {
        v(m_data);
    }

private:
    int m_data;
};

然后使用它:

data_list["first"]->visit(PrintVisitor{});
data_list["second"]->visit(PrintVisitor{});

当然,如果您已经知道地图中元素的类型,则可以使用static_cast向下转换它们。但这需要您有足够的上下文来预先知道将要使用的类型:

string first_data = get_value(static_cast<String_data*>((data_list["first"]));
int second_data = get_value(static_cast<Integer_data*>((data_list["second"]));

call_me(first_data, second_data);

我建议您不要使用dynamic_cast。当您要使用动态类型转换时,最好使用变体。用这种低调的方式暗示您知道将要处理的类型的数量,并且像变体一样约束层次结构,但是以更微妙和更容易出错的方式。


由于您为问题添加了更多详细信息,因此问题更加明确。

我的解决方案是不直接使用字符串,而使用某种标识符来封装如何强制转换和如何获取值。

struct house_data_t {
    static constexpr auto index = "house";
    using type = String_data; 
} inline constexpr house_data{};

struct loan_data_t {
    static constexpr auto index = "loan";
    using type = Integer_data; 
} inline constexpr loan_data{};

然后创建一个使用此元数据的函数:

std::string house = get_value(house_data, data_list);
int loan = get_value(loan_data, data_list);

get_value函数可以这样实现:

auto get_data(String_data* data) -> std::string {
    return data->str();
}

auto get_data(Integer_data* data) -> int {
    return data->value();
}

using data_list_t = std::map<std::string_view, std::unique_ptr<Data>>;


template<typename data_t, typename data_type_t = typename data_t::type>
auto get_value(data_t data, data_list_t& data_list) -> decltype(get_data(std::declval<data_type_t*>())) {
    return get_data(static_cast<data_type_t*>(data_list[data.index].get()));
}

Live example