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::variant
或 boost::any
。
但在很多情况下,您并不想这样做。 C++ 是一种很棒的静态类型语言,尝试强制它动态类型化(尤其是不节省内存使用)并不是您的最佳用例。请改用 actual 动态类型语言,因为它很可能针对该用例进行了更好的优化(并且更易于阅读)。
选项包括...
歧视工会
该代码指定一组不同的受支持类型 T0、T1、T2、T3...,并且 - 概念上 - 创建管理类型
struct X
{
enum { F0, F1, F2, F3... } type_;
union { T0 t0_; T1 t1_; T2 t2_; T3 t3_; ... };
};
因为可以放入 union
s 的类型有限制,如果使用 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&)
),然后为每个 short
和 long 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);
今天我和一位朋友讨论了静态类型语言和动态类型语言之间的区别(有关静态类型语言和动态类型语言之间区别的更多信息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::variant
或 boost::any
。
但在很多情况下,您并不想这样做。 C++ 是一种很棒的静态类型语言,尝试强制它动态类型化(尤其是不节省内存使用)并不是您的最佳用例。请改用 actual 动态类型语言,因为它很可能针对该用例进行了更好的优化(并且更易于阅读)。
选项包括...
歧视工会
该代码指定一组不同的受支持类型 T0、T1、T2、T3...,并且 - 概念上 - 创建管理类型
struct X
{
enum { F0, F1, F2, F3... } type_;
union { T0 t0_; T1 t1_; T2 t2_; T3 t3_; ... };
};
因为可以放入 union
s 的类型有限制,如果使用 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&)
),然后为每个 short
和 long 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);