9

"序章"

私はC ++に比較的慣れていません。私はすべての基本的なことを調べ、自分のプログラミング言語用に 2 ~ 3 個の単純なインタープリターを構築することができました。

最初に頭を悩ませ、今でも頭を悩ませているのは、自分の言語の型システムを C++ で実装することです。

考えてみてください。Ruby、Python、PHP、および Co. には、明らかに C で実装されている多くの組み込み型があります。そこで、私が最初に試みたのは、私の言語で値を与えることができるようにすることでした: Int、ストリングとニル。

私はこれを思いついた:

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、その位置を に書き込みindexます。の型が の場合、Value整数Intを に格納しただけですindex。int インデックスを使用して別の int にアクセスするのはまったく意味がありません。それとも?

とにかく、上記も頭を悩ませました。しばらくすると、ここのテーブルから文字列にアクセスし、そこで参照し、そこにコピーすると、頭がいっぱいになり、制御できなくなりました。私は通訳草稿を下に置かなければなりませんでした。

さて: C と C++ は静的に型付けされます。

  • 上記の言語の主な実装は、プログラム内のさまざまな型 (fixnums、bignums、nums、strings、arrays、resources など) をどのように処理しますか?

  • 利用可能なさまざまなタイプで最大速度を得るにはどうすればよいですか?

  • 上記の単純化されたバージョンと比較して、ソリューションはどうですか?

4

5 に答える 5

5

ここでできることはいくつかあります。さまざまなソリューションが登場し、それらのほとんどは実際のデータの動的割り当てを必要とします(boost :: Variantは、小さなオブジェクトに動的に割り当てられたメモリの使用を回避できます-@ MSaltersに感謝します)。

純粋なCアプローチ:

タイプ情報と、タイプ情報(通常は列挙型)に従って解釈する必要があるメモリへのvoidポインタを格納します。

enum type_t {
   integer,
   string,
   null
};
typedef struct variable {
   type_t type;
   void * datum;
} variable_t;
void init_int_variable( variable_t * var, int value )
{
   var->type = integer;
   var->datum = malloc( sizeof(int) );
   *((int)var->datum) = value;
}
void fini_variable( variable_t var ) // optionally by pointer
{
   free( var.datum );
}

C ++では、クラスを使用して使用法を簡素化することでこのアプローチを改善できますが、さらに重要なことに、より複雑なソリューションを選択し、同じ問題に対して異なるソリューションを提供するboost::anyまたはboost::variantとして既存のライブラリを使用できます。

boost::anyとboost::Variantはどちらも、動的に割り当てられたメモリに値を格納します。通常は、階層内の仮想クラスへのポインターを介して、具象型に再解釈(ダウンキャスト)する演算子を使用します。

于 2010-04-23T08:47:22.563 に答える
5

明らかな解決策の 1 つは、型階層を定義することです。

class Type
{
};

class Int : public Type
{
};

class String : public Type
{
};

等々。完全な例として、小さな言語のインタプリタを書きましょう。この言語では、次のように変数を宣言できます。

var a 10

Intこれにより、オブジェクトが作成され、それに値が割り当て10られ、名前で変数のテーブルに保存されますa。操作は変数に対して呼び出すことができます。たとえば、2 つの 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
于 2010-04-23T08:28:36.187 に答える
1

速度に関しては、次のように述べています。

文字列アロケータを常に呼び出す必要があるため、このクラスを渡すのは非常に時間がかかりました。

ほとんどの場合、参照によってオブジェクトを渡す必要があることを知っていますか? あなたのソリューションは、単純なインタープリターにとって実行可能に見えます。

于 2010-04-23T08:28:35.553 に答える
1

C++ は厳密に型指定された言語です。あなたは型付けされていない言語から来ており、それでもそれらの用語で考えていることがわかります。

複数の型を変数に格納する必要がある場合は、 boost ::anyを参照してください。

ただし、インタープリターを実装する場合は、特定の型を表す継承とクラスを使用する必要があります。

于 2010-04-23T08:41:53.557 に答える
0

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

彼のコードに欠けているのは、これらの値を抽出する方法です...これが私のバージョンです(実際、これはOgreから学び、好みに合わせて変更しました)。

使用法は次のようなものです:

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

Any クラスのコードを以下に示します。好きなように自由に使用してください:)。お役に立てれば!

ヘッダーファイルで... Any.hと言います

    #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;
}

今CPPファイルに... Any.cpp

#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;
}
于 2010-04-23T10:20:48.860 に答える