【问题标题】:A map in c++ which can accept any type of valuec++ 中的映射,可以接受任何类型的值
【发布时间】:2016-02-04 04:33:24
【问题描述】:

我想在 c++ 中创建一个可以接受任何类型值的映射,我在 java 中使用 Object 类做了同样的事情 映射但不知道如何在 C++ 中进行操作。 请帮忙。

【问题讨论】:

  • 如果您需要通用地图,请使用std::map。如果你想了解它是如何工作的,谷歌搜索“c++ 模板”会带来很多信息。
  • 我认为 C++ 中最接近的抽象是boost::any
  • 您可以通过创建class Object 并从中派生所有类型来在C++ 中执行完全相同的操作。但这真的取决于你想要做什么,最好的方法是什么。

标签: c++ maps


【解决方案1】:

正如上一个答案正确建议的那样,您不能在 C++ 中开箱即用。我假设 "[...] 可以接受任何类型的值 [...]" 您的意思是值,而不是地图的键。

不过,您可以这样做。你有两个选择;我会从丑变乖。

第一种方法:

  • 创建一个值保存类,您将使用它作为地图的value。我们称之为价值

  • 在该类中为您要支持的所有类型实现显式构造函数,并跟踪该类当前存储的值的类型

  • 从映射中获取值后检查值的类型并使用适当的getter函数

  • (可选)重载<< 运算符以支持标准流

有关示例实现,请参见以下代码:

#include <iostream>
#include <memory>
#include <map>

class Value {
public:
  typedef enum {
    String,
    Integer,
    Double,
    Float
  } ContentType;

private:
  ContentType m_ctType;

  std::string m_strContent;
  int m_nContent;
  double m_dContent;
  float m_fContent;

public:
  Value() : m_strContent(""), m_ctType(String) {}
  explicit Value(const char* arrcString) : m_strContent(std::string(arrcString)), m_ctType(String) {}
  explicit Value(std::string strContent) : m_strContent(strContent), m_ctType(String) {}
  explicit Value(int nContent) : m_nContent(nContent), m_ctType(Integer) {}
  explicit Value(double dContent) : m_dContent(dContent), m_ctType(Double) {}
  explicit Value(float fContent) : m_fContent(fContent), m_ctType(Float) {}

  ~Value() {}

  ContentType type() {
    return m_ctType;
  }

  std::string stringValue() { return m_strContent; }
  int integerValue() { return m_nContent; }
  double doubleValue() { return m_dContent; }
  float floatValue() { return m_fContent; }
};

std::ostream& operator<<(std::ostream& osStream, Value& valOut) {
  switch(valOut.type()) {
  case Value::String: osStream << valOut.stringValue(); break;
  case Value::Integer: osStream << valOut.integerValue(); break;
  case Value::Double: osStream << valOut.doubleValue(); break;
  case Value::Float: osStream << valOut.floatValue(); break;
  }

  return osStream;
}

这样可以使用:

int main() {
  std::map<int, Value> mapAnyValue;

  mapAnyValue[0] = Value("Test");
  mapAnyValue[1] = Value(1337);

  std::cout << mapAnyValue[0] << ", " << mapAnyValue[1] << std::endl;

  return 0;
}

这个输出

Test, 1337

现在有些人可能会说这是

  • 效率低下(它为每个 Value 实例中未使用的类型保留字段)
  • 难以扩展/维护(添加新字段有点麻烦)
  • 总体上糟糕的设计

他们是对的。所以这里是使用多态性和模板的替代方案。

第二种方法:

这需要您在将值分配给变量时定义要存储的值的类型,并且需要使用指针。原因如下。

对于这种方法,我们执行以下操作:

  • 创建一个基类ValueBase,作为我们可以作为类型放入地图的类。

  • 从这个类派生一个模板类Value&lt;T&gt;,它包含模板类型T的任意值。

  • 为了支持std::cout和朋友们,我们为ValueBase类实现&lt;&lt;的运算符重载,在ValueBase中添加一个纯虚函数output,并在Value&lt;T&gt;中重写此函数为您在模板中使用的任何类型使用默认的&lt;&lt; 运算符。

请参阅下面的代码示例:

#include <iostream>
#include <memory>
#include <map>

class ValueBase {
public:
  ValueBase() {}
  ~ValueBase() {}

  virtual void output(std::ostream& osStream) = 0;
};

template<typename T>
class Value : public ValueBase {
private:
  T m_tValue;

public:
  Value(T tValue) : m_tValue(tValue) {}
  ~Value() {}

  T value() {
    return m_tValue;
  }

  void output(std::ostream& osStream) override {
    osStream << m_tValue;
  }
};

std::ostream& operator<<(std::ostream& osStream, ValueBase& valOut) {
  valOut.output(osStream);

  return osStream;
}

这样可以使用:

int main() {
  std::map<int, std::shared_ptr<ValueBase>> mapAnyValue;

  mapAnyValue[0] = std::make_shared<Value<std::string>>("Test");
  mapAnyValue[1] = std::make_shared<Value<int>>(1337);

  std::cout << *mapAnyValue[0] << ", " << *mapAnyValue[1] << std::endl;

  return 0;
}

或者没有智能指针:

int main() {
  std::map<int, ValueBase*> mapAnyValue;

  mapAnyValue[0] = new Value<std::string>("Test");
  mapAnyValue[1] = new Value<int>(1337);

  std::cout << *mapAnyValue[0] << ", " << *mapAnyValue[1] << std::endl;

  delete mapAnyValue[0];
  delete mapAnyValue[1];

  return 0;
}

两个输出

Test, 1337

第二种方法在使用方面存在一些差异。

首先,您需要使用指针。这样做的原因是这样,成员函数vtable 被保留,您可以从派生类覆盖基类中的函数。在我们的情况下,这意味着:当我们在初始化为Value&lt;T&gt;ValueBase 类型的指针上调用output() 时,将使用来自Value&lt;T&gt;output() 函数,而不是来自ValueBase 的函数。如果您使用普通变量而不是指针,则将使用来自ValueBaseoutput() 函数,并且我们会丢失派生类中的信息。

第二个,这个和第一个有关,你需要在使用值的时候引用你得到的指针。如果要输出带有std::coutValueBaseValue&lt;T&gt; 指针,则需要将其作为std::cout &lt;&lt; *var 来输出包含的值。如果你只是做了std::cout &lt;&lt; var,你会正确地得到指针的地址。

我确信还有其他选择,尤其是在使用 Boost 时,但我不是这方面的专家。其他人可能对此有更有价值的信息。

除此之外,你正在做的事情听起来像是一种懒惰的行为。 C++ 有一个强类型系统是有原因的;它不仅定义明确,而且您还知道对代码的期望。如果你开始让事情变得模糊,并为各种任务使用任意容器对象,你的代码将失去可读性、清晰度,并且(很可能)会产生许多难以追踪、调试和最终修复的错误,因为你需要支持你引入的所有花哨的容器来保持你的框架运行。

如果你想使用 Java 这样的语言,最好使用 Java 而不是 C++。

【讨论】:

    【解决方案2】:

    你不能这样做。

    如果事先不知道要存储什么类型,则无法决定需要多少空间来存储它。

    您在 Java 中实际所做的并不是您在 C++ 中要求的,而是更类似于 std::map&lt;KeyType, shared_ptr&lt;void&gt;&gt;,然后在持有的指针上做了一大堆 dynamic_casts,给定一些虚拟接口 @987654323一切(包括intfloatchar)都源自@。

    在 C++ 中,您可以使用reinterpret_cast,而不是派生和使用dynamic_cast,并保留某种集合来记录您放入集合中的每个对象的类型。

    这是“反射”,C++ 还没有内置。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-29
      • 1970-01-01
      相关资源
      最近更新 更多