同时持有不同类型的集合
Collections holding different Types Simultaneously
传统上,我用c++和Java编程,现在我开始学习ruby。
那么我的问题是,像 ruby 这样的语言如何在内部实现它们的数组和哈希数据结构,以便它们可以同时保存任何类型的类型?我知道在 Java 中,每个类都派生自对象的事实可能是实现这一点的一种方法,但我想知道是否有另一种方法。例如,在 c++ 中,如果我想实现一个可以同时保存多种类型的值(无关系(的动态数组,我该怎么做呢?
澄清一下,我不是指泛型编程或模板,因为它们只是为类型创建新的集合接口。我指的是这样的结构:
array = [1, "hello", someClass];
它们中的大多数与你在C++中通过创建一个vector
(或list
、deque
等(boost::any
或类似的东西大致相同。
也就是说,它们基本上将一些标签附加到存储在内存中的每种类型的对象上。当他们存储对象时,他们会存储标签。当他们读取一个对象时,他们会查看标签以找出那是什么类型的对象。当然,它们也在内部处理大部分问题,因此您不必编写代码来确定刚刚从集合中检索到的对象类型。
如果不清楚:"标签"只是分配给每种类型的唯一编号。如果您正在处理的系统具有基元类型,它通常会为每个基元类型预先分配一个类型编号。同样,您创建的每个类都会获得分配给它的唯一编号。
若要在 C++ 中执行此操作,通常需要创建一个标记的中央注册表。注册类型时,将收到一个用于标记该类型对象的唯一编号。当语言直接支持此功能时,它会自动执行注册类型并为每个类型选择唯一标记的过程。
尽管这可能是实现此类操作的最常见方法,但它绝对不是唯一的方法。例如,还可以为特定类型指定特定的存储范围。分配给定类型的对象时,始终从该类型的地址范围分配该对象。当你创建一个"对象"的集合时,你实际上不是存储对象本身,而是存储包含对象地址的东西。由于对象是按地址隔离的,因此您可以根据指针的值确定对象的类型。
在 MRI 解释器中,红宝石值存储为指针类型,该指针类型指向存储值类以及与该值关联的任何数据的数据结构。由于指针总是相同的大小(通常sizeof(unsigned long)
(,这是可能的。要回答您关于C++的问题,C++不可能确定对象在内存中的位置,因此除非您有这样的东西,否则这是不可能的:
enum object_class { STRING, ARRAY, MAP, etc... };
struct TaggedObject {
enum object_class klass;
void *value;
}
并传递了TaggedObject *
值。这几乎就是Ruby内部所做的。
有很多方法可以做到这一点:-
您可以为所有元素定义一个通用接口,并制作这些元素的容器。例如:
class Common { /* ... */ }; // the common interface.
您可以使用void*
容器:-
vector<void*> common; // this would rather be too low level.
// you have to use cast very much.
然后我认为最好的方法是使用 Any 类,例如 Boost::Any :-
vector<boost::any> v;
您正在寻找一种称为类型擦除的东西。在C++中执行此操作的最简单方法是使用 boost::any:
std::vector<boost::any> stuff;
stuff.push_back(1);
stuff.push_back(std::string("hello"));
stuff.push_back(someClass);
当然,有了any
,你能用stuff
做的事情非常有限,因为你必须亲自记住你投入的一切。
异构容器更常见的用例可能是一系列回调。标准类std::function<R(Args...)>
实际上是一个类型擦除函子:
void foo() { .. }
struct SomeClass {
void operator()() { .. }
};
std::vector<std::function<void()>> callbacks;
callbacks.push_back(foo);
callbacks.push_back(SomeClass{});
callbacks.push_back([]{ .. });
在这里,我们将三个不同类型的对象(一个void(*)()
、一个SomeClass
和一些 lambda(添加到同一个容器中 - 我们通过擦除类型来实现。所以我们仍然可以做:
for (auto& func : callbacks) {
func();
}
这将在三个对象中的每一个中做正确的事情......无需虚拟!
其他人已经解释了在C++中执行此操作的方法。
有多种方法可以解决此问题。为了回答你关于Ruby等语言如何解决这个问题的问题,在不详细介绍Ruby如何解决这个问题的情况下,他们使用了一个包含类型信息的结构。例如,我们可以在C++这样的事情中做到这一点:
enum TypeKind { None, Int, Float, String }; // May need a few more?
class TypeBase
{
protected:
TypeKind kind;
public:
TypeBase(TypeKind k) : kind(k) { }
virtual ~TypeBase() {};
TypeKind type() { return kind; }
};
class TypeInt : public TypeBase
{
private:
int value;
public:
TypeInt(int v) : value(v), TypeBase(Int) {}
};
class TypeFloat : public TypeBase
{
private:
double value;
public:
TypeFloat(double v) : value(v), TypeBase(Float) {}
};
class TypeString : public TypeBase
{
private:
std::string value;
public:
TypeString(std::string v) : value(v), TypeBase(String) {}
};
(为了使其有用,我们可能需要为TypeXxx
类提供更多的方法,但我不想再打一个小时...... ;) (
然后在某个地方,它确定类型,例如
Token t = getNextToken();
TypeBase *ty;
if (t.type == IntegerToken)
{
ty = new(TypeInt(stoi(t.str));
}
else if (t.type == FloatToken)
{
ty = new(TypeFloat(stod(t.str));
}
else if (t.type == StringToken)
{
ty = new(TypeString(t.str));
}
当然,我们还需要处理变量和各种其他场景,但它的本质是语言可以跟踪(有时改变(存储的值。
Ruby,PHP,Python等一般类别中的大多数语言都将具有这种机制,并且所有变量都以某种间接方式存储。以上只是一种可能的解决方案,我至少可以想到六种其他方法可以做到这一点,但它们是"将数据与类型信息一起存储"主题的变体。
(顺便说一下,boost::any
也做了一些类似上述的事情,或多或少......
在 Ruby 中,答案相当简单:该数组不包含不同类型的值,它们都是同一类型的。它们都是对象。
Ruby 是动态类型的,静态约束数组只包含相同类型的元素的想法甚至没有意义。
对于静态类型语言,问题是,你希望它有多像 Ruby?是否希望它实际动态类型化?然后你需要用你的语言实现一个动态类型(如果它还没有,比如 C♯ 的 dynamic
(。
否则,如果你想要一个静态类型的异构列表,这样的东西通常被称为 HList
。例如,在Shapeless库中有一个非常好的Scala实现。
- 我收到同义重复编译器错误。我应该如何修复"类型"X"的参数与类型"X"的参数不兼容?
- C++中的集合(异构类型的数组)
- 整数到模板类型集合(C++)
- 将基元类型插入到集合中
- 为什么我不能在同一行中定义两个相同类型的类的成员指针
- 使用模板C++任何集合类型的包装器
- 如何从C 中的集合中检索多个继承类型
- 我可以按类类型访问哪些类型的集合,并在必要时返回未找到
- CRTP 模式 但是在数据结构中存储非同构类型
- 创建一个抽象类类型的集合,shared_ptr的抽象类向量
- C++集合方法:函数'setCost'不可行:'this'参数的类型'const value_type'
- C - 如何将自己类型的值插入集合中
- Boost:当缺少类型时,如何通过基指针序列化/反序列化泛型类型集合
- 集合类型的可变长度参数列表
- 私有结构(在类中定义)不能用作属于同一类的函数的返回类型吗
- 有没有可能拥有C++模板函数,它接受任何类型T的集合
- 管理模板化派生类型的向量集合
- 以前这样做过吗?(Monad视图包装用于链操作的c++集合/类型)
- 循环访问类中的不同集合类型
- 为类型集合生成标识符