【问题标题】:C++ and dynamically typed languagesC++ 和动态类型语言
【发布时间】:2016-04-12 00:17:34
【问题描述】:

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

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

因为我假设我要对所有这些进行计算,所以我希望 所有这些都以与从输入文件中读取它们相同的顺序存储在同一容器中的内存的连续位置中。强>。幼稚的方法是将它们存储在unsigned long long 类型的vector 中,但这意味着通常需要实际需要的空间的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: 问题不是关于节省内存,我知道在动态类型语言中存储示例中数字的必要空间将比在 C++ 中更多,但问题不在于那个,而是关于使用某种 c++ 机制模拟动态类型语言。

【问题讨论】:

  • 呃...哪个编译器允许您在 void* 上调用 delete
  • 是的,我知道我不应该……
  • 这有一个明显的缺点,即必须同时存储指针,但由于我假设无论如何我都会将它们存储在堆中,所以我认为这并不重要。 i> 如果您正在考虑将数字作为单独的内存分配存储在堆中,我认为在存储数字时使用过多内存的整个问题并不重要。您为此使用的内存比尝试使用不同类型所节省的内存要多得多。
  • 另外,警告也很简单:你分配一个短的内存,然后像分配一个长长的内存一样访问该内存。这超出了内存分配的限制,是一件非常糟糕的事情。

标签: c++ c++11 types programming-languages valgrind


【解决方案1】:

选项包括...

有区别的联合

代码指定了一组不同的、受支持的类型 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&lt;&gt;。请注意,type_ 字段需要一些空间,union 将至少与 sizeof t0_sizeof t1_... 中的最大值一样大,并且可能需要填充。

std::type_info

还可以有一个模板化的构造函数和赋值运算符调用typeid 并记录std::type_info,从而允许未来的操作,如“recover-the-value-if-it's-of-a-specific-type”。发现这种行为的最简单方法是使用boost::any

运行时多态

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

定制解决方案

在您的特定场景中,您只有几个大数字:您可以执行类似保留 short 值之一作为标记,指示该位置的实际值可以通过按位移位重新创建和以下 4 个值的 ORing。比如……

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 编码。这将非常节省空间,但它适合前向迭代,而不是像 [123] 那样随机访问索引。

【讨论】:

    【解决方案2】:

    您可以创建一个用于存储动态值的类:

    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);
    

    【讨论】:

    • 从 C++17 开始,标准库包含一个 std::variant 类型来存储动态值。
    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-12
        相关资源
        最近更新 更多