bug-bison
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: too many warnings from Bison CVS for Pike


From: Hans Aberg
Subject: Re: too many warnings from Bison CVS for Pike
Date: Sat, 25 Feb 2006 21:01:48 +0100

On 25 Feb 2006, at 17:36, Akim Demaille wrote:

There is no problem with storing a polymorphic pointer in a
variant.

The idea is to use the traditional C++ virtual hierarchy. This is the model that was introduced into C++ as an extension of the corresponding one in C.

Which is precisely what I refer to as a polymorphic pointer.

I used the word polymorphy wrongly (because I have not given so much thought to the "why", as I already have my working setup). It can be static (compile time only), as in the template system. The 'union' construct provides a dynamic one, essentially untyped, polymorphy, which avoids free store and its allocation time. When I started to use the polymorphic class hierarchy, it was recommended as a replacement of the union construction.

I fail to see the point of your comment.

You probably need to learn this C++ programing first, then.

Yeah, right.

I will need to check out the boost::variant a bit more :-); it may perhaps fill that gap, providing a C++ typed (POD type admitting) union. But I do not think it will be enough in itself in a more general setting.

I use a base class "object" with a reference count in it, by which other classes are derived as "public", (but not "virtual", if one should use static_cast). After playing around with different implementation models, I have found the one below the easiest to use, because when I need a new polymorphic base class, I only use ref<> defined below. Perhaps boost has something similar. But the stuff below is tailored to my needs, so I do not have any reason right now to look for something else.


#define clone_class(A) virtual A* clone() const { return new A (*this); } #define copy_class(A) virtual A* copy() const { increment_count(); return const_cast<A*>(this); }

class object {
  typedef unsigned long count_type;
  mutable count_type count_;
public:
  typedef object null_type;

  object() : count_(1) {}
  virtual ~object() {}

  object(const object&) : count_(1) {}

  void increment_count() const { ++count_; }
  count_type count() const { return count_; }

  clone_class(object);
  clone_class(object);

  void shed() { if (--count_ == 0)  delete this; }

virtual void write(std::ostream& os, write_style) const { os << "object"; }
};


Add a polymorphic class:

class X : public object {
  public:
    ...
    clone_class(X);
    clone_class(X);
    ...
};

Then, if I should create a polymorphic base class based on the interface of X, I use a template class ref<X>, which keeps track of a pointer to X, plus has some conversion operators in it, making the interface of X accessible.

template<class A>
class ref {
protected:
  mutable A* data_;
  static typename A::null_type null_;

public:
  typedef A&         reference;
  typedef A*         pointer;
  typedef const A&   const_reference;
  typedef const A*   const_pointer;

  typedef ref<A> This;

  ref() : data_(0) {}
  ~ref() { shed(); }

  ref(const ref& x) : data_(x.copy()) {}
  ref& operator=(const ref& x) {
    if (data_ != x.data_) { shed(); data_ = x.copy(); }
    return *this;
  }

  // Conversion constructors.
  ref(A* ap) : data_(ap) {}
  ref(const A* ap) : data_(ap->copy()) {}
  ref(const A& a) : data_(a.copy()) {}

  template<class B>
  explicit ref(B* bp, bool dynamic = true)
   : data_(dynamic? dynamic_cast<A*>(bp) : static_cast<A*>(bp)) {}

  template<class B>
  explicit ref(const B& br, bool dynamic = true)
: data_(dynamic? dynamic_cast<A*>(br.copy()) : static_cast<A*> (br.copy())) {}

  ref<A> clone() const { return (data_ == 0)? 0 : data_->clone(); }
  A* copy() const { return (data_ == 0)? 0 : data_->copy(); }

  void shed() { if (data_ != 0)  data_->shed(); }

  bool is_null() const { return (data_ == 0); }

  // Operators that return pointer 0 when applicable:

  operator A*() { return data_; }
  operator const A*() const { return data_; }

  // Operators that return reference to an object A() when applicable:

// Return a pointer to the referenced object, or if 0, the A::null_ object:
  A* operator->() { if (data_ == 0) return &null_; else return data_; }
const A* operator->() const { if (data_ == 0) return &null_; else return data_; }

// Return a reference to the referenced object, or if 0, the A::null_ object:
  A& operator*() { if (data_ == 0) return null_; else return *data_; }
const A& operator*() const { if (data_ == 0) return null_; else return *data_; }

  // Create an independent copy of the referenced object.
  ref<A> detach() const {
if (data_ != 0 && data_->count() > 1) { data_->shed(); data_ = data_->clone(); }
    return copy();
  }

// If 0, mutate to new A(). Return a reference to an independent copy of the
  // referenced object.
  A& operator+() const {
    if (data_ == 0)  data_ = new A();
else if (data_->count() > 1) { data_->shed(); data_ = data_- >clone(); }
    return *data_;
  }
};

Then ref<X> becomes this polymorphic base class; some of these base classes X are in fact virtual (abstract), making it impossible to store a value of them (as in a boost::variant), but only usable as a pointer in ref<X>.

Then ref<X> is the class to be used in the Bison static type system. The Bison semantic type will then typically just contain ref<object>, as conversions between ref<object> and ref<X> will be possible. One should then be able to use the static_cast conversions of this class. But in the subsequent runtime program, after the parse has been done, dynamic_cast must be used; this is the reason there are static_cast/ dynamic_cast pairs in some of the conversion operators. In the code above, I use some special stuff that should probably be removed or simplified at some point, but which has to do with the origin of the code. For example, the object null_ above is used to specify the behavior of ref<X>() by implementing a special class. The class X must the contain something like
  class X : public Y {
  public:
    typedef X_null null_type;
    ...
  };

  class X_null : public X {...};
If not absolutely needed, null_ should be removed.

As it happens, I do not need the function clone() in the parser actions, as new objects will be created by new dynamic allocations. I think I may use it somewhere in the program though. Also, this program does not need a handle; but I have another program that requires it, as objects can self-mutate to one of a different static type, and it is also using a union together with a type in the form of an 'enum' as well, to choose between different function pointers, a pointer and a handle.

  Hans Aberg






reply via email to

[Prev in Thread] Current Thread [Next in Thread]