Topic: Towards an object class for C++


Author: german diago <germandiago@gmail.com>
Date: Wed, 2 May 2012 11:46:42 -0700 (PDT)
Raw View
Hello all,

I would like to put here, in order to be discussed, what I think it's
still a missing point in the C++ standard library.

I'm trying to implement an object class for c++, like C# and java
have, but adapted to the c++ needs. My class is targeted at c++11 (not
c++03 for now). The main goals I want to achieve are:

1.- Plays well with all kind of c++ types as much as possible.
2.- It feels comfortable and high level when used, looks as a high-level type.
3.- It's safe to use, no segfaults or whatever. Use of exceptions.


For my implementation, I've already decided (although not set in stone):

  - Use type erasure. Client types don't need to derive from any class
to use object class.
  - Value semantics. Should be used by value, not by pointer.
  - Value type requirements: at least movable type.
  - Able to hold both polymorphic and non-polymorphic types without
the held types losing polymorphism.
  - Convertible to string.

Planned:
  - Serializable.
  - Equality comparable.
  - Hashable (to be useful in containers).
  - Assignable to concrete types (like POCO DynamicAny class
http://pocoproject.org/docs/Poco.DynamicAny.html).

I'm facing one very concrete problem:

 - I want that function getReference() from object can cast an object
to its concrete type or, if polymorphic, to any of its base classes.
When the held value in object is not polymorphic, it's easy, but when
you want to hold a polymorphic type and be able to cast to a base
class, things get more complicated.

I tried with a CastTo<T> class like the following that is stored in a
global container for class Object for each concrete type held in an
object. When the constructor of Object is executed:

- Since the exact type of the held type T is known, I create an entry
for CastTo<T> with key typeid(T).name(), to be able to cast back to
the concrete type again later.


template <class T>
struct CastTo {
   void * operator(ObjectInterface & o) const;
};

The problem is that I must return a void * because I need to be able
to hold a map of the CastTo<T> function objects in a global container
for the class to be able to cast back to ObjectModel<T> type.

After that, I can cast to the base class type, but I cannot apply
dynamic_cast<BaseClass*> to a void pointer, so I need alternative
implementations or a solution for this problem. It's working now, but
it's not a safe cast.

The interface for the class right now. (Still needs a lot of review)


class Object {
public:
  template <class T>
  Object(T obj);

  Object(const Object &) = delete;
  Object & operator=(const Object &) = delete;


  Object(Object &&) = default;
  Object & operator=(Object &&) = default;


  template <class T>
  bool isA() const;

  template <class T>
  T extractAs() const;

  template <class T>
  T & getReference() const;

  template <class T>
  T & getConstReference() const;


  bool isPolymorphic() const;
  std::string toString() const;

private:
  std::unique_ptr<ObjectInterface> held_object_;
};

My ObjectInterface is a class like the following:

class ObjectInterface {
public:
  virtual std::string toString() const = 0;
  virtual bool isPolymorphic() const = 0;
  virtual ~ObjectInterface() {}
};

My holder for concrete types of Objects is like this:

template <class T>
class ObjectModel : public ObjectInterface {
public:
  ObjectModel(T value);

  ObjectModel(const ObjectModel &) = delete;
  ObjectModel(ObjectModel &&) = delete;
  ObjectModel & operator=(const ObjectModel) = delete;
  ObjectModel & operator=(ObjectModel &&) = delete;

  bool isPolymorphic() const;


  std::string toString() const;

  bool isPolymorphic_;
  T value_;
};


Regards,

Germ   n Diago G   mez


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]




Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?=<daniel.kruegler@googlemail.com>
Date: Fri, 4 May 2012 11:23:51 -0700 (PDT)
Raw View
Am 02.05.2012 20:46, schrieb german diago:
>  I would like to put here, in order to be discussed, what I think it's
>  still a missing point in the C++ standard library.
>
>  I'm trying to implement an object class for c++, like C# and java
>  have, but adapted to the c++ needs. My class is targeted at c++11 (not
>  c++03 for now). The main goals I want to achieve are:
>
>  1.- Plays well with all kind of c++ types as much as possible.
>  2.- It feels comfortable and high level when used, looks as a high-level type.
>  3.- It's safe to use, no segfaults or whatever. Use of exceptions.
>
>  For my implementation, I've already decided (although not set in stone):
>
>     - Use type erasure. Client types don't need to derive from any class
>  to use object class.
>     - Value semantics. Should be used by value, not by pointer.
>     - Value type requirements: at least movable type.

 From what you show so far, you Object wrapper is some fancy unique_ptr,
thus the wrapper can only moved, never copied (cloned). While this is
often right, for other use-cases different strategies this might be a
bit limited, either cloning or shared-pointer-like semantics often is
preferred.

>     - Able to hold both polymorphic and non-polymorphic types without
>  the held types losing polymorphism.

I assume that only applies to the sliced object that is has been wrapped
by moving into the wrapper, right? I guess that you do not realize full
polymorphism for wrapped *pointers*, for example?

>     - Convertible to string.

Just out of interest: What is your suggested "canonical" to_string form
for a type that has no operator<<  useable for an std::ostream object?
I'm asking, because this is the "usual" problem for types like
dynamic_any. Java's analogue expressed as

std::string(typeid(T).name()) + '@' + boost::lexical_cast<std::string>(&t)

is what I decided for, but I'm curious for your decision.

>  Planned:
>     - Serializable.
>     - Equality comparable.

In this case, you also need a fall-back for lacking operator==. Will
this be address-identity? I think, this *can* be problematic for an
otherwise value-like type (I decided for not providing it for this reason)

>     - Hashable (to be useful in containers).
>     - Assignable to concrete types (like POCO DynamicAny class
>  http://pocoproject.org/docs/Poco.DynamicAny.html).

I would be interested in your approach for the conversion. My current
guess is that it works "roughly" for conversions to arithmetic types,
presumably based via some emulated conversion to long double (for
example) but I don't see how this can be done for general conversions,
including from class type A to class type B.

>  I'm facing one very concrete problem:
>
>    - I want that function getReference() from object can cast an object
>  to its concrete type or, if polymorphic, to any of its base classes.
>  When the held value in object is not polymorphic, it's easy, but when
>  you want to hold a polymorphic type and be able to cast to a base
>  class, things get more complicated.

An often found technique is one where you would specialize ObjectModel
depending on whether it is a class type or not. If it is a class type,
you would change its specialization to

template<class T>
class ObjectModel : public T, ObjectInterface { [..] };

which works for all except final classes. To prevent unintentional
overriding of ObjectInterface's functions by those of T, one possible
trick is that you invent an empty tag interface, like

struct nested_object {
   virtual ~nested_object() {}
};

and derive

template<class T>
class nested_class : public T, nested_object { };

You need to have a single function within your type-erased wrapper that
returns a nested_object*. You need to ensure that it will return nullptr
for non-class types, otherwise you can perform the dynamic cast to any
type starting from nested_object*.

Other techniques are possible, where nested_class is not necessarily
empty (Like ObjectModel above). In this case you can prevent any
unintended overriding by introducing a unique signature type

struct unique_signature {};

and use this in all function of the wrapper.

In my own dynamic_any I used the first approach and I could easily
realize some form of reduced dynamic casts (I still think, it is
reduced, because I only perform this to actual classes, not to pointers
or references of classes.)

HTH&  Greetings from Bremen,

Daniel Kr   gler


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]