同时持有不同类型的集合

时间:2014-12-21 17:42:49

标签: c++ ruby

传统上,我用c ++和Java编程,现在我开始学习ruby。

我的问题是,像ruby这样的语言如何在内部实现数组和哈希数据结构,以便它们可以同时保存任何类型?我知道在Java中,每个类都是从对象派生的,这可能是实现这一点的一种方法,但我想知道是否还有其他方法。例如,在c ++中,如果我想实现一个可以同时保存多种类型值(无关系)的动态数组,我该怎么办呢?

为了澄清,我没有提到通用编程或模板,因为它们只是为类型创建一个新的集合接口。我指的是这样的结构:

array = [1, "hello", someClass];

6 个答案:

答案 0 :(得分:5)

通过创建vector的{​​{1}}(或listdeque等),或者大部分内容与您在C ++中获得的大致相同,或者相似的东西。

也就是说,它们基本上将一些标记附加到每种类型的对象,因为它存储在内存中。当他们存储对象时,他们存储标记。当他们读取一个对象时,他们会查看标签以确定是什么类型的对象。当然,它们也在内部处理大部分内容,因此您不必编写代码来确定您刚刚从集合中检索到的对象类型。

如果不清楚:“标签”只是分配给每种类型的唯一编号。如果您正在处理的系统具有原始类型,则通常会为每个系统预先分配一个类型编号。同样,您创建的每个类都会获得一个分配给它的唯一编号。

要在C ++中执行此操作,您通常会创建一个标记的中央注册表。注册类型时,会收到一个用于标记该类型对象的唯一编号。当一种语言直接支持它时,它会自动执行注册类型和为每个类型选择唯一标记的过程。

虽然这可能是实现此类事情的最常用方法,但它绝对不是唯一的方法。例如,也可以为特定类型指定特定的存储范围。分配给定类型的对象时,始终从该类型的地址范围分配。当您创建“对象”的集合时,您实际上并不是存储对象本身,而是存储包含对象地址的内容。由于对象是按地址隔离的,因此您可以根据指针的值来确定对象的类型。

答案 1 :(得分:4)

MRI解释器中,ruby值存储为指针类型,指向存储值类的数据结构以及与该值关联的任何数据。由于指针总是相同的大小(通常为sizeof(unsigned long)),因此是可能的。要回答你关于C ++的问题,在C ++中不可能确定一个对象的类,因为它在内存中的位置,所以除非你有这样的东西,否则它是不可能的:

enum object_class { STRING, ARRAY, MAP, etc... };

struct TaggedObject {
  enum object_class klass;
  void *value;
}

传递TaggedObject *个值。这就是ruby在内部所做的事情。

答案 2 :(得分:2)

有很多方法可以做到: -

您可以为所有元素定义一个公共接口,并创建一个容器。例如:

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;

答案 3 :(得分:2)

你正在寻找一种叫做类型擦除的东西。在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();
}

这将在三个对象中的每个对象中做正确的事情......不需要虚拟化!

答案 4 :(得分:2)

其他人已经解释了如何在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也可以做上述内容,或多或少....)

答案 5 :(得分:0)

在Ruby中,答案很简单:数组不包含包含不同类型的值,它们都是相同的类型。它们都是物体。

Ruby是动态类型的,静态约束的数组只能容纳相同类型元素的想法甚至没有意义。

对于静态类型语言,问题是,您希望它像Ruby一样多少?你想让它实际上是动态输入的吗?然后你需要用你的语言实现一个动态类型(如果它还没有,就像C♯的dynamic)。

否则,如果你想要一个静态类型的异类列表,这种东西通常称为HList。例如,在Shapeless库中Scala的实现非常好。