C++ 和动态类型语言

C++ and dynamically typed languages

今天我和一位朋友讨论了静态类型语言和动态类型语言之间的区别(有关静态类型语言和动态类型语言之间区别的更多信息in this SO question)。在那之后,我想知道在 C++ 中可以使用什么样的技巧来 模拟 这种动态行为。

在 C++ 中,与在其他静态类型语言中一样,变量类型在编译时指定。例如,假设我必须从文件中读取大量数字,这些数字在大多数情况下都非常小,小到足以放入 unsigned short 类型。棘手的事情来了,这些值中的一小部分要大得多,大到需要 unsigned long long 来存储。

因为我假设我要对所有这些进行计算,所以我希望 所有它们都存储在同一个容器中的连续内存位置中,顺序与我从输入中读取它们的顺序相同文件。。天真的方法是将它们存储在 unsigned long long 类型的 vector 中,但这意味着通常需要额外 space 实际需要的 4 倍(unsigned short 2字节,unsigned long long 8 个字节)。

在动态类型语言中,变量的类型在运行时被解释并强制转换为适合的类型。 如何在 C++ 中实现类似的功能?

我的第一个想法是通过指针来实现,根据它的大小,我会用适当的类型存储数字。这有一个明显的缺点,那就是必须同时存储指针,但由于我假设无论如何我都会将它们存储在堆中,所以我认为这并不重要。

我完全相信你们中的许多人能给我比这更好的解决方案...

#include <iostream>
#include <vector>
#include <limits>
#include <sstream>
#include <fstream>

int main() {
    std::ifstream f ("input_file");
    if (f.is_open()) {
        std::vector<void*> v;
        unsigned long long int num;
        while(f >> num) {
            if (num > std::numeric_limits<unsigned short>::max()) {
                v.push_back(new unsigned long long int(num));
            }
            else {
                v.push_back(new unsigned short(num));
            }
        }
        for (auto i: v) {
            delete i;
        }
    f.close();
    }
}

编辑 1: 问题 与节省内存无关 ,我知道在动态类型语言中,示例中存储数字所必需的 space 比在 C++ 中要多得多,但是问题不在于此,而是关于使用某些 c++ 机制模拟动态类型语言。

在 C++ 中获得动态语言行为的一种简单方法是使用动态语言引擎,例如Javascript.

或者,例如,Boost 库提供了 Python 的接口。

可能这会比您自己处理数字集合更有效,但与仅在 C++ 中使用适当的单个通用类型相比,它仍然非常低效。

C++ 中动态类型的正常方式是 boost::variantboost::any

但在很多情况下,您并不想这样做。 C++ 是一种很棒的静态类型语言,尝试强制它动态类型化(尤其是不节省内存使用)并不是您的最佳用例。请改用 actual 动态类型语言,因为它很可能针对该用例进行了更好的优化(并且更易于阅读)。

选项包括...

歧视工会

该代码指定一组不同的受支持类型 T0、T1、T2、T3...,并且 - 概念上 - 创建管理类型

struct X
{
    enum { F0, F1, F2, F3... } type_;
    union { T0 t0_; T1 t1_; T2 t2_; T3 t3_; ... };
};

因为可以放入 unions 的类型有限制,如果使用 placement-new 绕过它们,需要注意确保充分对齐和正确的析构函数调用,一般化的实现会变得更复杂,通常使用 boost::variant<> 会更好。请注意,type_ 字段需要一些 space,union 将至少与 sizeof t0_sizeof t1_... 和填充中的最大者一样大可能需要。

std::type_info

也可以有一个调用 typeid and record the std::type_info 的模板化构造函数和赋值运算符,允许像 "recover-the-value-if-it's-of-a-specific-type" 这样的未来操作。获取此行为的最简单方法是使用 boost::any.

运行-时间多态性

您可以使用虚拟析构函数和您需要的任何函数创建一个基类型(例如 virtual void output(std::ostream&)),然后为每个 shortlong long 派生一个 class .存储指向基数 class.

的指针

自定义解决方案

在您的特定情况下,您只有几个大数字:您可以做一些事情,例如保留 short 值之一作为标记,指示可以重新创建此位置的实际值通过对以下 4 个值进行位移和或运算。例如...

10 299 32767 0 0 192 3929 38

...可以编码:

10
299
// 32767 is a sentinel indicating next 4 values encode long long
(0 << 48) + (0 << 32) + (192 << 16) + 3929
38

这里的概念类似于国际字符集的UTF-8编码。这将非常 space 高效,但它适合前向迭代,而不适合随机访问索引 [123].

您可以创建一个 class 来存储动态值:

enum class dyn_type {
  none_type,
  integer_type,
  fp_type,
  string_type,
  boolean_type,
  array_type,
  // ...
};

class dyn {
  dyn_type type_ = dyn_type::none_type;
  // Unrestricted union:
  union {
    std::int64_t integer_value_;
    double fp_value_;
    std::string string_value_;
    bool boolean_value_;
    std::vector<dyn> array_value_;
  };
public:
  // Constructors
  dyn()
  {
     type_ = dyn_type::none_type;
  }
  dyn(std::nullptr_t) : dyn() {}
  dyn(bool value)
  {
    type_ = dyn_type::boolean_type;
     boolean_value_ = value;
  }
  dyn(std::int32_t value)
  {
    type_ = dyn_type::integer_type;
     integer_value_ = value;
  }
  dyn(std::int64_t value)
  {
     type_ = dyn_type::integer_type;
     integer_value_ = value;
  }
  dyn(double value)
  {
     type_ = dyn_type::fp_type;
     fp_value_ = value;
  }
  dyn(const char* value)
  {
     type_ = dyn_type::string_type;
     new (&string_value_) std::string(value);
  }
  dyn(std::string const& value)
  {
     type_ = dyn_type::string_type;
     new (&string_value_) std::string(value);
  }
  dyn(std::string&& value)
  {
     type_ = dyn_type::string_type;
     new (&string_value_) std::string(std::move(value));
  }
  // ....

  // Clear
  void clear()
  {
     switch(type_) {
     case dyn_type::string_type:
       string_value_.std::string::~string();
       break;
     //...
     }
     type_ = dyn_type::none_type;
  }
  ~dyn()
  {
    this->clear();
  }

  // Copy:
  dyn(dyn const&);
  dyn& operator=(dyn const&);

  // Move:
  dyn(dyn&&);
  dyn& operator=(dyn&&);

  // Assign:
  dyn& operator=(std::nullptr_t);
  dyn& operator=(std::int64_t);
  dyn& operator=(double);
  dyn& operator=(bool);   

  // Operators:
  dyn operator+(dyn const&) const;
  dyn& operator+=(dyn const&);
  // ...

  // Query
  dyn_type type() const { return type_; }
  std::string& string_value()
  {
     assert(type_ == dyn_type::string_type);
     return string_value_;
  }
  // ....

  // Conversion
  explicit operator bool() const
  {
    switch(type_) {
    case dyn_type::none_type:
      return true;
    case dyn_type::integer_type:
      return integer_value_ != 0;
    case dyn_type::fp_type:
      return fp_value_ != 0.0;
    case dyn_type::boolean_type:
      return boolean_value_;
    // ...
    }
  }
  // ...
};

用于:

std::vector<dyn> xs;
xs.push_back(3);
xs.push_back(2.0);
xs.push_back("foo");
xs.push_back(false);