Boost logo

Boost :

Subject: Re: [boost] How to create a shallow copy without callingaconstructor?
From: vicente.botet (vicente.botet_at_[hidden])
Date: 2010-01-03 12:59:24


Hi Stefan, thanks for your comments
----- Original Message -----
From: "Stefan Strasser" <strasser_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Sunday, January 03, 2010 4:55 AM
Subject: Re: [boost] How to create a shallow copy without callingaconstructor?

>
> Am Saturday 02 January 2010 23:34:51 schrieb vicente.botet:
>> I want to define a generic mixin that define these function, something
>> like:
>>
>> template <class Derived, typename Base=base_transaction_object>
>> class shallow_transaction_object : public Base
>> {
>> public:
>> base_transaction_object* shallow_clone() const {
>> Derived* p = reinterpret_cast<Derived*>( new char[sizeof(Derived)]);
>> std::memcpy(p, static_cast<Derived const*>(this), sizeof(Derived));
>> return p;
>> }
>>
>> void cache_deallocate() {
>> delete[] reinterpret_cast<char*>(this);
>> }
>>
>> void copy_state(base_transaction_object const * const rhs) {
>> std::memcpy(static_cast<Derived *>(this), static_cast<Derived const *
>> const>(rhs), sizeof(Derived )); }
>> };
>>
>> So the user can just declare its own class as
>>
>> class C : public shallow_transaction_object<C> {
>> // members concerning the user space
>> };
>>
>> Can this be done in a portable way?
>
> depends on if you really mean portable or "defined" (by C++ standard).

I meant portable.

> I doubt memcpy()ing objects with a virtual pointer is defined, but I think
> it'll work on all implementations I know of if you always copy the most
> derived type.

This will always be the case. I should rename the template parameter Derived by Final.

template <class Final, typename Base=base_transaction_object>
class shallow_transaction_object;

> what types can class C : shallow_transaction_object contain? can it contain
> pointers or is it limited to "shallow types"?

it can contain any non pointer type and pointers to transactional objects, but no pointer to non-transactional objects.
The pointee transactional objects do not need to be copied since the STM system track any modification to them.

> for the latter, you might want to make the decision if memcpy() can be used
> based on boost::serialization::is_bitwise_serializable.

is_bitwise_serializable don't goes too far. it is equivalent to is_artithmetic. But it can be specialized.

I have find in Boost.TypeTraits this example

An Optimized Version of std::copy
Demonstrates a version of std::copy that uses has_trivial_assign to determine whether to use memcpy to optimise the copy operation.

maybe I need something like has_trivial_copy and has_trivial_assign.
 
> is the object in the cache used or is it only stored to be copied back to the
> exact same position of the original object, in case the transaction fails?

This depends on the updating policy. With direct updating the cached objects is used as a backup and used to restablish the shared object in case of rollback, so it is never used directly, with deferred updating, the cached object is the object modified by the transaction and used to update the shared object in case of succesfull commit.

For what you and Mathias said, and as I suspected the code is neither portable nor correct and limited to types having trivial copy semantics.

  template <class Derived, typename Base=base_transaction_object>
  class trivial_transactional_object : public Base
  {
    typedef Base base_type;
  public:
    base_transaction_object* make_cache() const {
      Derived* p = reinterpret_cast<Derived*>( new char[sizeof(Derived)]);
      std::memcpy(p, static_cast<Derived const*>(this), sizeof(Derived));
      return p;
    }

    void remove_cache() {
        delete[] reinterpret_cast<char*>(this);
    }

    void copy_cache(base_transaction_object const * const rhs) {
      std::memcpy(static_cast<Derived *>(this), static_cast<Derived const * const>(rhs), sizeof(Derived ));
    }
  };

For shallow copy semantics, I suspect that I will need to relay on whether the user defines these shallow copy constructor and assignement operations following these prototypes

  struct shallow_t {};
  const shallow_t shallow = {};

  C(C const&, shallow_t);
  C& shallow_assign(C const&);

The new shallow_transactional_object could be defined as

  template <class Derived, typename Base=base_transaction_object>
  class shallow_transactional_object : public Base {
  public:
    base_transaction_object* make_cache() const {
      Derived* p = reinterpret_cast<Derived*>( new char[sizeof(Derived)]);
      return new(p) Derived(this, shallow);
    }

    void remove_cache() {
        delete[] reinterpret_cast<char*>(this);
    }

    void copy_cache(base_transaction_object const * const rhs) {
      this->shallow_assign(static_cast<Derived const * const>(rhs));
    }
  };

Now I can define transactional_object depending on whether has_trivial_copy_semantics<T>::value , has_shallow_copy_semantics<T>::value, or has_deep_copy_semantics<T>::value (TBD)

 namespace detail {
   template <class Final, typename Base, bool hasShallowCopySemantics, bool hasTrivialCopySemantics>
   class transactional_object;
   template <class Final, typename Base>
   class transactional_object<Final, Base, true, true>: public shallow_transactional_object<Derived, Base> {};
   template <class Final, typename Base>
   class transactional_object<Final, Base, true, true>: public shallow_transactional_object<Derived, Base> {};
   template <class Final, typename Base>
   class transactional_object<Final, Base, false,true>: public trivial_transactional_object<Derived, Base> {};
   template <class Derived, typename Base>
   class transactional_object<Final, Base, false,false>: public deep_transactional_object<Derived, Base> {};
 }
  template <class Final, typename Base=base_transaction_object>
  class transactional_object
    : public detail::transactional_object<Final, Base, has_shallow_copy_semantics<Derived>::value, has_trivial_copy_semantics<Derived>::value>
 {};

Of course, this force the user to define specific shallow copy semantics operations, but IMO this will have some advantages. What do you think of this approach?

Anyway, I find that it is a shame that the compiler know how to make a shallow copy-constructor and shallow assignement operator, but one the user defines a specific one we are unable to use them. Do you think that its is worth to have the possibility to use the default generated constructors and assignement operators in C++ ? For example we could use these defaulted operators as follows

C a(default); // use the default constructor generated by the compiler
...
C b(a, default); // use the default copy constructor generated by the compiler

C c;

std::assign_default(c, a); // use the default assignement operator generated by the compiler for class C

Best,
Vicente


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk