C++ C++;及其类型系统:如何处理多种类型的数据?
“简介” 我对C++比较陌生。我完成了所有的基本工作,并设法为我的编程语言构建了2-3个简单的解释器 第一件让我头疼的事情是:用C++实现我语言的类型系统。 想想看:Ruby、Python、PHP和Co.有很多内置类型,它们显然是用C实现的。 所以我首先尝试的是用我的语言给一个值三种可能的类型:Int,String和Nil 我想到了这个:C++ C++;及其类型系统:如何处理多种类型的数据?,c++,interpreter,typing,C++,Interpreter,Typing,“简介” 我对C++比较陌生。我完成了所有的基本工作,并设法为我的编程语言构建了2-3个简单的解释器 第一件让我头疼的事情是:用C++实现我语言的类型系统。 想想看:Ruby、Python、PHP和Co.有很多内置类型,它们显然是用C实现的。 所以我首先尝试的是用我的语言给一个值三种可能的类型:Int,String和Nil 我想到了这个: enum ValueType { Int, String, Nil }; class Value { public: ValueType t
enum ValueType
{
Int, String, Nil
};
class Value
{
public:
ValueType type;
int intVal;
string stringVal;
};
是啊,哇,我知道。传递这个类非常慢,因为必须一直调用字符串分配器
下次我尝试类似的方法时:
enum ValueType
{
Int, String, Nil
};
extern string stringTable[255];
class Value
{
public:
ValueType type;
int index;
};
我会将所有字符串存储在stringTable
中,并将它们的位置写入索引。如果值
的类型是Int
,我只是将整数存储在索引中
,那么使用Int索引访问另一个Int根本没有意义,或者
不管怎么说,以上这些也让我头疼。过了一段时间,从这里的表中访问字符串,在那里引用它,然后在那里复制它,这些都让我不知所措——我失去了控制。我不得不把翻译稿放下
现在:好,C和C++是静态类型的。
- 上述语言的主要实现如何处理其程序中的不同类型(fixnums、bignums、nums、字符串、数组、资源等)
- 我应该怎么做才能在多种不同的可用类型下获得最高速度
- 这些解决方案与我上面的简化版本相比如何
关于速度,你说:
通过这个过程非常缓慢
类作为字符串分配器
不得不一直打电话
你知道你大部分时间都应该通过引用传递对象吗?对于简单的解释器,您的解决方案看起来是可行的。一个明显的解决方案是定义类型层次结构:
class Type
{
};
class Int : public Type
{
};
class String : public Type
{
};
等等。作为一个完整的例子,让我们为一种小型语言编写一个解释器。该语言允许声明如下所示的变量:
var a 10
这将创建一个Int
对象,为其赋值10
,并将其存储在变量表中,名为a
。可以对变量调用操作。例如,对两个Int值的加法操作如下所示:
+ a b
以下是解释器的完整代码:
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <cstdlib>
#include <map>
// The base Type object from which all data types are derived.
class Type
{
public:
typedef std::vector<Type*> TypeVector;
virtual ~Type () { }
// Some functions that you may want all types of objects to support:
// Returns the string representation of the object.
virtual const std::string toString () const = 0;
// Returns true if other_obj is the same as this.
virtual bool equals (const Type &other_obj) = 0;
// Invokes an operation on this object with the objects in args
// as arguments.
virtual Type* invoke (const std::string &opr, const TypeVector &args) = 0;
};
// An implementation of Type to represent an integer. The C++ int is
// used to actually store the value. As a consequence this type is
// machine dependent, which might not be what you want for a real
// high-level language.
class Int : public Type
{
public:
Int () : value_ (0), ret_ (NULL) { }
Int (int v) : value_ (v), ret_ (NULL) { }
Int (const std::string &v) : value_ (atoi (v.c_str ())), ret_ (NULL) { }
virtual ~Int ()
{
delete ret_;
}
virtual const std::string toString () const
{
std::ostringstream out;
out << value_;
return out.str ();
}
virtual bool equals (const Type &other_obj)
{
if (&other_obj == this)
return true;
try
{
const Int &i = dynamic_cast<const Int&> (other_obj);
return value_ == i.value_;
}
catch (std::bad_cast ex)
{
return false;
}
}
// As of now, Int supports only addition, represented by '+'.
virtual Type* invoke (const std::string &opr, const TypeVector &args)
{
if (opr == "+")
{
return add (args);
}
return NULL;
}
private:
Type* add (const TypeVector &args)
{
if (ret_ == NULL) ret_ = new Int;
Int *i = dynamic_cast<Int*> (ret_);
Int *arg = dynamic_cast<Int*> (args[0]);
i->value_ = value_ + arg->value_;
return ret_;
}
int value_;
Type *ret_;
};
// We use std::map as a symbol (or variable) table.
typedef std::map<std::string, Type*> VarsTable;
typedef std::vector<std::string> Tokens;
// A simple tokenizer for our language. Takes a line and
// tokenizes it based on whitespaces.
static void
tokenize (const std::string &line, Tokens &tokens)
{
std::istringstream in (line, std::istringstream::in);
while (!in.eof ())
{
std::string token;
in >> token;
tokens.push_back (token);
}
}
// Maps varName to an Int object in the symbol table. To support
// other Types, we need a more complex interpreter that actually infers
// the type of object by looking at the format of value.
static void
setVar (const std::string &varName, const std::string &value,
VarsTable &vars)
{
Type *t = new Int (value);
vars[varName] = t;
}
// Returns a previously mapped value from the symbol table.
static Type *
getVar (const std::string &varName, const VarsTable &vars)
{
VarsTable::const_iterator iter = vars.find (varName);
if (iter == vars.end ())
{
std::cout << "Variable " << varName
<< " not found." << std::endl;
return NULL;
}
return const_cast<Type*> (iter->second);
}
// Invokes opr on the object mapped to the name var01.
// opr should represent a binary operation. var02 will
// be pushed to the args vector. The string represenation of
// the result is printed to the console.
static void
invoke (const std::string &opr, const std::string &var01,
const std::string &var02, const VarsTable &vars)
{
Type::TypeVector args;
Type *arg01 = getVar (var01, vars);
if (arg01 == NULL) return;
Type *arg02 = getVar (var02, vars);
if (arg02 == NULL) return;
args.push_back (arg02);
Type *ret = NULL;
if ((ret = arg01->invoke (opr, args)) != NULL)
std::cout << "=> " << ret->toString () << std::endl;
else
std::cout << "Failed to invoke " << opr << " on "
<< var01 << std::endl;
}
// A simple REPL for our language. Type 'quit' to exit
// the loop.
int
main (int argc, char **argv)
{
VarsTable vars;
std::string line;
while (std::getline (std::cin, line))
{
if (line == "quit")
break;
else
{
Tokens tokens;
tokenize (line, tokens);
if (tokens.size () != 3)
{
std::cout << "Invalid expression." << std::endl;
continue;
}
if (tokens[0] == "var")
setVar (tokens[1], tokens[2], vars);
else
invoke (tokens[0], tokens[1], tokens[2], vars);
}
}
return 0;
}
/home/me $ ./mylang
var a 10
var b 20
+ a b
30
+ a c
Variable c not found.
quit
C++是一种强类型语言。我可以看出你来自一种非类型语言,并且仍然用这些术语思考
如果您真的需要在一个变量中存储多个类型,请查看
但是,如果您要实现一个解释器,那么您应该使用继承和表示特定类型的类。这里可以做一些不同的事情。不同的解决方案及时出现,其中大多数都需要动态分配实际数据(boost::variant可以避免为小对象使用动态分配的内存——感谢@MSalters)
纯C方法:
存储类型信息和指向必须根据类型信息(通常是枚举)进行解释的内存的空指针:
<>在C++中,可以通过使用类来简化用法来改进这种方法,但更重要的是,您可以使用更复杂的解决方案,并将现有的库用作Booo::任何或Boosi::提供相同问题的不同解决方案的变型。
boost::any和boost::variant都将值存储在动态分配的内存中,通常通过指向层次结构中虚拟类的指针,并使用重新解释(向下转换)到具体类型的运算符。根据Vijay的解决方案,实现将是:
Type* array;
// to initialize the array
array = new Type(size_of_array);
// when you want to add values
array[0] = new Int(42);
// to add another string value
array[1] = new String("fourty two");
他的代码缺少的一点是如何提取这些值。。。这是我的版本(实际上我是从食人魔那里学来的,并根据自己的喜好对它进行了修改)
用法类似于:
Any array[4];
// Automatically understands it's an integer
array[0] = Any(1);
// But let's say you want the number to be thought of as float
array[1] = Any<float>(2);
// What about string?
array[2] = Any<std::string>("fourty two");
// Note that this gets the compiler thinking it's a char*
// instead of std::string
array[3] = Any("Sometimes it just turns out to be what you don't want!");
大多数时候,这并不容易。由于许多函数临时修改了值,我不得不复制了几份。@sub-Well这听起来很可疑。我不明白为什么在创建一个值后,你会更改它,除非用户分配给它。我可以将它们全部存储在一个数组中吗?或者它们是完全不同的类型,比如string和int?我对这个比较陌生。@Vijay:你能解释一下如何以及在哪里存储这些类的实例吗?这是许多系统中常见的解决方案。它必须与动态分配相结合——这是我提供的C解决方案的直接演变,在这里您添加层次结构并用动态转换替换C样式转换。实际的类型信息通常存储在基类中(作为枚举),或通过typeid()获得operator@sub最简单的C++方法是使用STD::MAP作为符号表。我将用一个更完整的例子来修改我的答案。“Int,String,Nil”,那么float呢?我完全支持不使用float
的编程语言的想法!否则,关于SO的第一个sub_语言问题将是“嘿,为什么sub_语言中的0.1+0.2==0.3?它坏了!”@sub:如果是你对某些答案投了否决票,你应该重新考虑。有一些有效的答案被无缘无故地否决了,其中一些可能是你没有完全理解答案。如果不是你,无论是谁做的,请解释你认为错误的答案。这是改善系统的唯一方法,这不是我的问题。你没有告诉我如何以及在哪里存储这些类的实例,我已经投票了,因为答案很好:使用boost::any。我看不出这不是你的问题。boost::any是书面问题的解决方案,即使它不是sub头脑中问题的(完整)答案。忘了提及。速度-w
Any array[4];
// Automatically understands it's an integer
array[0] = Any(1);
// But let's say you want the number to be thought of as float
array[1] = Any<float>(2);
// What about string?
array[2] = Any<std::string>("fourty two");
// Note that this gets the compiler thinking it's a char*
// instead of std::string
array[3] = Any("Sometimes it just turns out to be what you don't want!");
if(array[2].isType<std::string>()
{
// Extract the string value.
std::string val = array[2].cast<std::string>();
// Make the string do your bidding!!!... /evilgrin
// WAIT! But what if you want to directly manipulate
// the value in the array?
std::string& val1 = array[2].cast<std::string>();
// HOHOHO... now any changes to val1 affects the value
// in the array ;)
}
#include <typeinfo>
#include <exception>
/*
* \class Any
* \brief A variant type to hold any type of value.
* \detail This class can be used to store values whose types are not
* known before hand, like to store user-data.
*/
class Any
{
public:
/*!
* \brief Default constructor.
*/
Any(void);
/*!
* \brief Constructor that accepts a default user-defined value.
* \detail This constructor copies that user-defined value into a
* place holder. This constructor is explicit to avoid the compiler
* to call this constructor implicitly when the user didn't want
* the conversion to happen.
* \param val const reference to the value to be stored.
*/
template <typename ValueType>
explicit Any(const ValueType& val);
/*!
* \brief Copy constructor.
* \param other The \c Any variable to be copied into this.
*/
Any(const Any& other);
/*!
* \brief Destructor, does nothing other than destroying the place holder.
*/
~Any(void);
/*!
* \brief Gets the type of the value stored by this class.
* \detail This function uses typeid operator to determine the type
* of the value it stores.
* \remarks If the place holder is empty it will return Touchscape::VOID_TYPE.
* It is wise to check if this is empty by using the function Any::isEmpty().
*/
const std::type_info& getType() const;
/*!
* \brief Function to verify type of the stored value.
* \detail This function can be used to verify the type of the stored value.
* Usage:
* \code
* int i;
* Touchscape::Any int_any(i);
* // Later in your code...
* if (int_any.isType<int>())
* {
* // Do something with int_any.
* }
* \endcode
* \return \c true if the type matches, false otherwise.
*/
template <typename T>
bool isType() const;
/*!
* \brief Checks if the type stored can be converted 'dynamically'
* to the requested type.
* \detail This would useful when the type stored is a base class
* and you would like to verify if it can be converted to type
* the user wants.
* Example:
* \code
* class Base
* {
* // class implementation.
* };
* class Derived : public Base
* {
* // class implementation.
* };
*
* // In your implementation function.
* {
* //...
* // Somewhere in your code.
* Base* a = new Derived();
* Touchscape::Any user_data(a);
* my_object.setUserData(user_data);
* // Then when you need to know the user-data type
* if(my_object.getUserData().isDynamicType<Derived>())
* {
* // Do something with the user data
* }
* }
* \endcode
* \return \c true if the value stored can be dynamically casted to the target type.
* \deprecated This function will be removed and/or changed in the future.
*/
template <typename T>
bool isDynamicType() const;
/*!
* \brief Convert the value stored to the required type.
* \detail This function is used just like a static-cast to retrieve
* the stored value.
* \return A reference to the stored value.
* \warning This function will throw std::bad_cast exception if it
* finds the target type to be incorrect.
*/
template <typename T>
T& cast();
/*!
* \brief Convert the value stored to the required type (const version).
* \detail This function is used just like static_cast to retrieve
* the stored value.
* \return A \c const reference to the stored value.
* \warning This function will throw std::bad_cast exception if it
* finds the target type to be incorrect.
*/
template <typename T>
const T& cast() const;
/*!
* \brief Dynamically converts the stored value to the target type
* \detail This function is just like dynamic_cast to retrieve
* the stored value to the target type.
* \return A reference to the stored value.
* \warning This function will throw std::bad_cast exception if it
* finds that the value cannot be dynamically converted to the target type.
* \deprecated This function will be removed and/or changed in the future.
*/
template <typename T>
T& dynamicCast();
/*!
* \brief Dynamically converts the stored value to the target type (const version)
* \detail This function is just like dynamic_cast to retrieve
* the stored value to the target type.
* \return A const reference to the stored value.
* \warning This function will throw std::bad_cast exception if it
* finds that the value cannot be dynamically converted to the target type.
* \deprecated This function will be removed and/or changed in the future.
*/
template <typename T>
const T& dynamicCast() const;
/*!
* \brief Swaps the contents with another \c Any variable.
* \return reference to this instance.
*/
Any& swap(Any& other);
/*!
* \brief Checks if the place holder is empty.
* \return \c true if the the place holder is empty, \c false otherwise.
*/
bool isEmpty() const;
/*!
* \brief Checks if the place holder is \b not empty.
* \return \c true if the the place holder is not empty, \c false otherwise.
* \remarks This is just a lazy programmer's attempt to make the code look elegant.
*/
bool isNotEmpty() const;
/*!
* \brief Assignment operator
* \detail Assigns a 'raw' value to this instance.
* \return Reference to this instance after assignment.
*/
template <typename ValueType>
Any& operator = (const ValueType& rhs);
/*!
* \brief Default assignment operator
* \detail Assigns another \c Any type to this one.
* \return Reference to this instance after assignment.
*/
Any& operator = (const Any& rhs);
/*!
* \brief Boolean equality operator
*/
bool operator == (const Any& other) const;
/*!
* \brief Boolean equality operator that accepts a 'raw' type.
*/
template<typename ValueType>
bool operator == (const ValueType& other) const;
/*!
* \brief Boolean inequality operator
*/
bool operator != (const Any& other) const;
/*!
* \brief Boolean inequality operator that accepts a 'raw' type.
*/
template<typename ValueType>
bool operator != (const ValueType& other) const;
protected:
/*!
* \class PlaceHolder
* \brief The place holder base class
* \detail The base class for the actual 'type'd class that stores
* the value for T
ouchscape::Any.
*/
class PlaceHolder
{
public:
/*!
* \brief Virtual destructor.
*/
virtual ~PlaceHolder(){}
/*!
* \brief Gets the \c type_info of the value stored.
* \return (const std::type_info&) The typeid of the value stored.
*/
virtual const std::type_info& getType() const = 0;
/*!
* \brief Clones this instance.
* \return (PlaceHolder*) Cloned instance.
*/
virtual PlaceHolder* clone() const = 0;
};
/*!
* \class PlaceHolderImpl
* \brief The class that ultimately keeps hold of the value stored
* in Touchscape::Any.
*/
template <typename ValueType>
class PlaceHolderImpl : public PlaceHolder
{
public:
/*!
* \brief The only constructor allowed.
* \param val The value to store.
*/
PlaceHolderImpl(const ValueType& val)
:m_value(val){}
/*!
* \brief The destructor.
* \detail Does nothing
*/
~PlaceHolderImpl(){}
/*!
* \copydoc Touchscape::PlaceHolder::getType()
*/
const std::type_info& getType() const
{
return typeid(ValueType);
}
/*!
* \copydoc Touchscape::PlaceHolder::clone()
*/
PlaceHolder* clone() const
{
return new PlaceHolderImpl<ValueType>(m_value);
}
ValueType m_value;
};
PlaceHolder* m_content;
};
/************************************************************************/
/* Template code implementation section */
/************************************************************************/
template <typename ValueType>
Any::Any(const ValueType& val)
:m_content(new PlaceHolderImpl<ValueType>(val))
{
}
//---------------------------------------------------------------------
template <typename T>
bool Any::isType() const
{
bool result = m_content?m_content->getType() == typeid(T):false;
return result;
}
//---------------------------------------------------------------------
template <typename T>
bool Any::isDynamicType() const
{
bool result = m_content
?dynamic_cast<T>(static_cast<PlaceHolderImpl<T>*>(m_content)->m_value)!=NULL
:false;
return result;
}
//---------------------------------------------------------------------
template <typename T>
T& Any::cast()
{
if (getType() != VOID_TYPE && isType<T>())
{
T& result = static_cast<PlaceHolderImpl<T>*>(m_content)->m_value;
return result;
}
StringStream ss;
ss<<"Cannot convert '"<<getType().name()<<"' to '"<<typeid(T).name()<<"'. Did you mean to use dynamicCast() to cast to a different type?";
throw std::bad_cast(ss.str().c_str());
}
//---------------------------------------------------------------------
template <typename T>
const T& Any::cast() const
{
Any& _this = const_cast<Any&>(*this);
return _this.cast<T>();
}
//---------------------------------------------------------------------
template <typename T>
T& Any::dynamicCast()
{
T* result = dynamic_cast<T>(static_cast<PlaceHolderImpl<T>*>(m_content)->m_value);
if (result == NULL)
{
StringStream ss;
ss<<"Cannot convert '"<<getType().name()<<"' to '"<<typeid(T)<<"'.";
throw std::bad_cast(ss.str().c_str());
}
return *result;
}
//---------------------------------------------------------------------
template <typename T>
const T& Any::dynamicCast() const
{
Any& _this = const_cast<Any&>(*this);
return _this.dynamicCast<T>();
}
//---------------------------------------------------------------------
template <typename ValueType>
Any& Any::operator = (const ValueType& rhs)
{
Any(rhs).swap(*this);
return *this;
}
//---------------------------------------------------------------------
template <typename ValueType>
bool Any::operator == (const ValueType& rhs) const
{
bool result = m_content == rhs;
return result;
}
//---------------------------------------------------------------------
template <typename ValueType>
bool Any::operator != (const ValueType& rhs) const
{
bool result = m_content != rhs;
return result;
}
#include "Any.h"
static const std::type_info& VOID_TYPE(typeid(void));
Any::Any( void )
:m_content(NULL)
{
}
//---------------------------------------------------------------------
Any::Any( const Any& other )
:m_content(other.m_content?other.m_content->clone():NULL)
{
}
//---------------------------------------------------------------------
Any::~Any( void )
{
SafeDelete(m_content);
}
//---------------------------------------------------------------------
const std::type_info& Any::getType() const
{
return m_content?m_content->getType():VOID_TYPE;
}
//---------------------------------------------------------------------
Any& Any::swap( Any& other )
{
std::swap(m_content, other.m_content);
return *this;
}
//---------------------------------------------------------------------
Any& Any::operator=( const Any& rhs )
{
Any(rhs).swap(*this);
return *this;
}
//---------------------------------------------------------------------
bool Any::isEmpty() const
{
bool is_empty = m_content == NULL;
return is_empty;
}
//---------------------------------------------------------------------
bool Any::isNotEmpty() const
{
bool is_not_empty = m_content != NULL;
return is_not_empty;
}
//---------------------------------------------------------------------
bool Any::operator==( const Any& other ) const
{
bool result = m_content == other.m_content;
return result;
}
//---------------------------------------------------------------------
bool Any::operator!=( const Any& other ) const
{
bool result = m_content != other.m_content;
return result;
}