可以接受任何类型值的 c++ 中的映射
A map in c++ which can accept any type of value
我想在 C++ 中创建一个可以接受任何类型值的映射,我在 java 中使用对象 class 做了同样的事情
映射但不知道如何在 C++ 中进行映射。
请帮忙。
你不能那样做。
如果事先不知道要存储的是什么类型,就无法决定需要存储多少space。
你在 Java 中实际做的不是你在 C++ 中要求的,而是更类似于 std::map<KeyType, shared_ptr<void>>
,然后在 dynamic_cast
上做一团乱麻持有的指针,给定一些虚拟接口 Object
,一切(包括 int
、float
和 char
)都从中派生。
在 C++ 中,您可以使用 reinterpret_cast
而不是派生和使用 dynamic_cast
,并保留某种集合来记录您放入集合中的每个对象的类型。
这是 "reflection",C++ 还没有内置它。
正如前面的答案正确建议的那样,您不能在 C++ 中开箱即用。我假设 "[...] 可以接受任何类型的值 [...]" 你的意思是值,而不是地图的键。
不过,您可以执行以下操作。你有两个选择;由丑变美
第一种方法:
创建一个包含 class 的值,您将用作地图的 value
。我们称它为 Value.
在 class 中为您要支持的所有类型实现显式构造函数,并跟踪 class 当前存储的值类型
从地图获取值后检查值的类型并使用适当的 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
实例中使用的类型保留字段)
- 很难extend/maintain(添加新字段有点麻烦)
- 一般设计不好
他们是对的。所以这是使用多态性和模板的替代方法。
第二种方法:
这需要您在将值分配给变量时定义要存储的值的类型,并且需要使用指针。原因如下。
对于这种方法,我们执行以下操作:
创建一个基础 class ValueBase
作为 class 我们可以把我们的地图作为 value类型。
从这个class派生一个模板化的classValue<T>
,它包含模板类型T
的任意值。
为了支持std::cout
和朋友们,我们为<<
实现了运算符重载classValueBase
,添加一个纯虚拟的[=26] =] 函数添加到 ValueBase
,并在 Value<T>
中覆盖此函数,以便为您在模板中使用的任何类型使用默认的 <<
运算符。
请参阅下面的代码示例:
#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被保留下来,你可以从派生的classes中覆盖基class中的函数.在我们的情况下,这意味着:当我们在初始化为 Value<T>
的 ValueBase
类型的指针上调用 output()
时,来自 Value<T>
的 output()
函数是使用而不是来自 ValueBase
。如果您使用普通变量而不是指针,则会使用 ValueBase
中的 output()
函数,并且我们会丢失派生的 class.
中的信息
其次,这与第一个有关,你需要在使用值时引用你得到的指针。如果要用std::cout
输出一个ValueBase
或Value<T>
指针,需要按std::cout << *var
输出包含的值。如果您只是 std::cout << var
,您将正确地获取指针的地址。
我确定还有其他选择,尤其是在使用 Boost 时,但我不是这方面的专家。其他人可能对此有更多有价值的信息。
除此之外,你所做的听起来像是一种懒惰行为。 C++ 有一个强类型系统是有原因的;它不仅定义明确,而且您还知道对代码的期望。如果你开始让事情变得模糊,并为各种任务使用任意容器对象,你的代码将失去可读性和清晰度,并且(很可能)会产生大量很难跟踪、调试和最终修复的错误,因为你需要支持你引入的所有花哨的容器来保持你的框架 运行.
如果你想使用像Java这样的语言,最好使用Java而不是C++。
我想在 C++ 中创建一个可以接受任何类型值的映射,我在 java 中使用对象 class 做了同样的事情 映射但不知道如何在 C++ 中进行映射。 请帮忙。
你不能那样做。
如果事先不知道要存储的是什么类型,就无法决定需要存储多少space。
你在 Java 中实际做的不是你在 C++ 中要求的,而是更类似于 std::map<KeyType, shared_ptr<void>>
,然后在 dynamic_cast
上做一团乱麻持有的指针,给定一些虚拟接口 Object
,一切(包括 int
、float
和 char
)都从中派生。
在 C++ 中,您可以使用 reinterpret_cast
而不是派生和使用 dynamic_cast
,并保留某种集合来记录您放入集合中的每个对象的类型。
这是 "reflection",C++ 还没有内置它。
正如前面的答案正确建议的那样,您不能在 C++ 中开箱即用。我假设 "[...] 可以接受任何类型的值 [...]" 你的意思是值,而不是地图的键。
不过,您可以执行以下操作。你有两个选择;由丑变美
第一种方法:
创建一个包含 class 的值,您将用作地图的
value
。我们称它为 Value.在 class 中为您要支持的所有类型实现显式构造函数,并跟踪 class 当前存储的值类型
从地图获取值后检查值的类型并使用适当的 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
实例中使用的类型保留字段) - 很难extend/maintain(添加新字段有点麻烦)
- 一般设计不好
他们是对的。所以这是使用多态性和模板的替代方法。
第二种方法:
这需要您在将值分配给变量时定义要存储的值的类型,并且需要使用指针。原因如下。
对于这种方法,我们执行以下操作:
创建一个基础 class
ValueBase
作为 class 我们可以把我们的地图作为 value类型。从这个class派生一个模板化的class
Value<T>
,它包含模板类型T
的任意值。为了支持
std::cout
和朋友们,我们为<<
实现了运算符重载classValueBase
,添加一个纯虚拟的[=26] =] 函数添加到ValueBase
,并在Value<T>
中覆盖此函数,以便为您在模板中使用的任何类型使用默认的<<
运算符。
请参阅下面的代码示例:
#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被保留下来,你可以从派生的classes中覆盖基class中的函数.在我们的情况下,这意味着:当我们在初始化为 Value<T>
的 ValueBase
类型的指针上调用 output()
时,来自 Value<T>
的 output()
函数是使用而不是来自 ValueBase
。如果您使用普通变量而不是指针,则会使用 ValueBase
中的 output()
函数,并且我们会丢失派生的 class.
其次,这与第一个有关,你需要在使用值时引用你得到的指针。如果要用std::cout
输出一个ValueBase
或Value<T>
指针,需要按std::cout << *var
输出包含的值。如果您只是 std::cout << var
,您将正确地获取指针的地址。
我确定还有其他选择,尤其是在使用 Boost 时,但我不是这方面的专家。其他人可能对此有更多有价值的信息。
除此之外,你所做的听起来像是一种懒惰行为。 C++ 有一个强类型系统是有原因的;它不仅定义明确,而且您还知道对代码的期望。如果你开始让事情变得模糊,并为各种任务使用任意容器对象,你的代码将失去可读性和清晰度,并且(很可能)会产生大量很难跟踪、调试和最终修复的错误,因为你需要支持你引入的所有花哨的容器来保持你的框架 运行.
如果你想使用像Java这样的语言,最好使用Java而不是C++。