Topic: proposal: renew & relocator member fn


Author: "john (j.d.) hickin" <hickin@bnr.ca>
Date: 1996/02/10
Raw View
|> Go ahead, shoot me down!

POW!

Seriously, how can the compiler tell when deep-copy is or isn't needed.  You
can help it, however, by using auto_ptr for the 'external' parts of an object
(i.e., those logically part of it but included through a pointer; this term is
my own so don't expect others to use it; I am looking for a better term).

With auto_ptr's in place your relocate operation is a breeze -- just copy the
object with a copy constructor and placement new.

This seems to be an ideal application for certain semantics of auto_ptr that
have been considered to be offensive.  To be maximally const-correct your
class will need a non-const copy constructor.

--
John Hickin      Nortel Technology, Montreal, Quebec
(514) 765-7924   hickin@bnr.ca
---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  Moderation policy:
  http://reality.sgi.com/employees/austern_mti/std-c++/policy.html. ]





Author: Dietmar Kuehl <Dietmar.Kuehl@uni-konstanz.de>
Date: 1996/02/12
Raw View
Hi,

Gregory Bond <gnb@bby.com.au> wrote:
> People often ask for a "renew[]" operator to complement "new[]" in the
> same way realloc() complements malloc().  Of course the problem there
> is contructing the new objects and destructing the old ones, so a
> whole bunch of code gets written to do a new[], a bunch of assigns and a
> delete[]. This (or the moral equivalent using malloc(), placement new
> and delete) is especially prevalent inside container classes such
> as STL.

I can only see one (minor) advantage to use 'realloc()' instead of a
more flexible method (see below): You can make use of the size of
memory object known to the memory system. If you accept that you
"waste" the storage to remember the actual size of a memory object you
can do without 'renew' or 'realloc()' using a "traits" mechanism.

> I was thinking about the efficiency concerns here.  For what is
> conceptually a simple operation, three calls are made - a default
> ctor, a copy ctor and a dtor.  The copy ctor may potentially have to

Most likely, two operations are to be made: a placement call to the
ctor and a call to the dtor. It should be avoided to construct an
object twice (I'm not sure but I think constructing an object twice
results in undefined behavior)!

> allocate and deep-copy extensive data structures, so this can be very
> expensive, especially when shallow copy would be sufficient if the
> copy-ctor could KNOW that the source object was about to be destroyed.
>
> So it seems to me that a combination of copy constructor and destructor
> could be used in these circumstances to "relocate" an object.  You
> could call this special member a "relocator".  This could offer
> substantial efficiency gains, especially in situations like
> vector<vector<A> >.

I agree with you that the copy constructor is sometimes a bad choice to
be used for doing a reallocation. A more appropriate constructor, a
"move constructor", can often be used: This constructor initializes one
object by moving all relevant members from one object into another one
leaving the original object in a state which is useless except for
destruction (this sometimes involves additional operations like setting
pointers to zero). This makes exactly use of the knowledge you want to
use for your "relocator" function. All what remains is to tell a
container class which does the actual relocation that it should use a
pair of "move constructor" and destructor calls. One possibility to do
so is to gather this information and other relevant information in a
"Traits" template argument to the container which contains 'static'
member functions to be used for certain operations.  Here are general
and specific "traits" which define a method for a movement:

  // The general 'move_traits' are useful for all classes defining
  // a copy constructor. Classes with other needs can define
  // specializations of this struct.

  template <class T>
  struct move_traits
  {
    T *move(T &source, void *target)
    {
      T *rc = new(target) T(source);
      source.~T();
      return rc;
    }
  };

  class Moveable
  {
  public:
    // pass a 'bool' for a different signature than the copy ctor
    Moveable(Moveable &, bool);
    // ...
  };

  struct move_traits<Moveable>
  {
    T *move(T &source, void *target)
    {
      T *rc = new(target) T(source, true);
      source.~T();
      return rc;
    }
  };

This solution of the move problem might require more work when
implementing a "moveable" class than the 'renew' approach. But is does
not require an extension of the language which is large enough without
'renew'. A problem might be that the STL containers do not use this
approach to move an object but I think this could be solved easily if
it is really desired. For an example of a container class (which is not
completely finished) using such an approach you can have a look at

  http://www.informatik.uni-konstanz.de/~kuehl/c++/array.h.html

Another approach is to use smart-pointers: Use a smart-pointer with
"move on copy" semantic (like 'auto_ptr'; is it defined like this in
the current version of the DWP?) or a "copy on write" semantic
involving some reference counting. Instead of copying the whole object,
a pointer to the object is copied. Unfortunately, the Standard Library
does not include a complete set of smart pointers. The only class of
this category is 'auto_ptr' with rather controversive semantics. This
is probably the reason why there are no other smart pointers included:
It is hard to find a reasonable consensus what they should do...

In general, I don't see why a 'renew' operator would be necessary.
Although it wouldn't cost anything for the portions of the code which
do not use it, it does not solve any problem which cannot be solved
with the current definition of the language.
--
dietmar.kuehl@uni-konstanz.de
http://www.informatik.uni-konstanz.de/~kuehl
I am a realistic optimist - that's why I appear to be slightly pessimistic
---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  Moderation policy:
  http://reality.sgi.com/employees/austern_mti/std-c++/policy.html. ]





Author: claus@faerber.muc.de (Claus A. Faerber)
Date: 1996/02/14
Raw View
Gregory Bond <gnb@bby.com.au> (06 Feb 96):

> [Yes, I know it's too late to consider new features.  So consider this
> advance notice for the next round!]
>
> People often ask for a "renew[]" operator to complement "new[]" in the
> same way realloc() complements malloc().  Of course the problem there
> is contructing the new objects and destructing the old ones, so a
> whole bunch of code gets written to do a new[], a bunch of assigns and a
> delete[]. This (or the moral equivalent using malloc(), placement new
> and delete) is especially prevalent inside container classes such
> as STL.
>
> I was thinking about the efficiency concerns here.  For what is
> conceptually a simple operation, three calls are made - a default
> ctor, a copy ctor and a dtor.  The copy ctor may potentially have to
> allocate and deep-copy extensive data structures, so this can be very
> expensive, especially when shallow copy would be sufficient if the
> copy-ctor could KNOW that the source object was about to be destroyed.
>
> So it seems to me that a combination of copy constructor and destructor
> could be used in these circumstances to "relocate" an object.  You
> could call this special member a "relocator".  This could offer
> substantial efficiency gains, especially in situations like
> vector<vector<A> >.
>
> The first step would be to pick a syntax to specify the relocator.
> This can't be an overloaded ctor (as any ctor arg list could
> conceivably be a valid ctor).  One posibility is to overload the dtor:
>       class A {
>               ~A(); // destruct this
>               ~A(A&to);  // Contruct to and destruct this
>               // or perhaps ~A(void *to) as it will be passed an
>               // address of empty memory, not an object.  But then
>               // you need to cast "to" to an "A*".
>       }

What about this syntax:?

class b {
        b();            // std. ctor
        b(~b& old);     // 'move' ctor

        void fn(int, b& old);   // other function
        void fn(int,~b& old);   // other function, destructing old

        operator=( b& oth);     // = operator
        operator=(~b& old);     // = operator, destruction source
}

The syntax: if a ~ leads the type of a function parameter,
it says that this function takes an object which is already
to be destructed and that the function does the destruction
itsself.
('destruction' means the same as what is done in a normal
constructor, ie. especially not freeing the memory used for
the object)

The rule would be that "when a compiler has to destruct an
object after it has been used by the function/ctor/operator,
it can look for a 'destructing function' and leave the
actual destruction to this fucntion."

Of yourse, these functions would have to be a 'friend' of0,
the class.

Eg.:

b b1, b2;
b& get_some_b();

b1 = b2;        // calls normal operator=(b&)

fn( b2 );       // calls normal fn(int,b*old)

b1 = get_some_b();
                // calls get_some_b(), which returns a
                // reference to a temporary b, which is
                // destructed by b1.operator(~b& old)

b2.fn( 123, b() );
                // the new b created to be passed to the
                // function is destructed by fn().

This syntax would be more flexible than your 'copy
destructor' as it allows _any_ function to make use of the
fact that it can (and must) destroy the passed object.

> Another possibility is to hijack another character and use it for the
> relocator in the same way ~ is used for the dtor. "@" seems somewhat
> mnemonic for relocation but this introduces a new character into the
> C++ character set. A third posability is to have a special member
> function with an actual name (e.g. "relocator(A&)") but this is ugly.
>
> The default relocator would be a copy ctor followed by a dtor.
>
> One a relocator is defined, we could define renew[] (along with
> operator renew[]):

>       X *p = new[10] X;
>       //...
>       renew[20](p); // 10 relocators and 10 def ctors called
>       // Or, if the existing memory block is big enough, the
>       // relocator isn't called at all, and 10 def ctors are called.

I think, it should be

        p = renew [20] p;
        ^^^^

>       delete [] p;


Claus

------------------------------------------------------------------------
Claus Andre Faerber - claus@faerber.muc.de - http://www.muc.de/~cfaerber
------------------------------------------------------------------------
---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  Moderation policy:
  http://reality.sgi.com/employees/austern_mti/std-c++/policy.html. ]





Author: gnb@bby.com.au (Gregory Bond)
Date: 1996/02/06
Raw View
[Yes, I know it's too late to consider new features.  So consider this
advance notice for the next round!]

People often ask for a "renew[]" operator to complement "new[]" in the
same way realloc() complements malloc().  Of course the problem there
is contructing the new objects and destructing the old ones, so a
whole bunch of code gets written to do a new[], a bunch of assigns and a
delete[]. This (or the moral equivalent using malloc(), placement new
and delete) is especially prevalent inside container classes such
as STL.

I was thinking about the efficiency concerns here.  For what is
conceptually a simple operation, three calls are made - a default
ctor, a copy ctor and a dtor.  The copy ctor may potentially have to
allocate and deep-copy extensive data structures, so this can be very
expensive, especially when shallow copy would be sufficient if the
copy-ctor could KNOW that the source object was about to be destroyed.

So it seems to me that a combination of copy constructor and destructor
could be used in these circumstances to "relocate" an object.  You
could call this special member a "relocator".  This could offer
substantial efficiency gains, especially in situations like
vector<vector<A> >.

The first step would be to pick a syntax to specify the relocator.
This can't be an overloaded ctor (as any ctor arg list could
conceivably be a valid ctor).  One posibility is to overload the dtor:
 class A {
  ~A(); // destruct this
  ~A(A&to);  // Contruct to and destruct this
  // or perhaps ~A(void *to) as it will be passed an
  // address of empty memory, not an object.  But then
  // you need to cast "to" to an "A*".
 }

Another possibility is to hijack another character and use it for the
relocator in the same way ~ is used for the dtor. "@" seems somewhat
mnemonic for relocation but this introduces a new character into the
C++ character set. A third posability is to have a special member
function with an actual name (e.g. "relocator(A&)") but this is ugly.

The default relocator would be a copy ctor followed by a dtor.

One a relocator is defined, we could define renew[] (along with
operator renew[]):
 X *p = new[10] X;
 //...
 renew[20](p); // 10 relocators and 10 def ctors called
 // Or, if the existing memory block is big enough, the
 // relocator isn't called at all, and 10 def ctors are called.

 delete[] p;

Or, for situations such as the STL that manage raw memory via
placement-new and destructors:
 X*p = (X*)malloc(10 * sizeof(X));
 for (int i = 0; i < 10; i++)
  new(p + i) X();
 //...
 X* new_p = (X*)realloc(p, 20 * sizeof(X));
 if (new_p == p) {
  // Was extended
  for (int i = 10; i < 20; i++)
   new (p + i) X();
 } else {
  // Was moved
  // Note that realloc() has done a superfluous byte copy here
  for (int i = 0; i < 10; i++)
   (p+i)->~X(new_p + i);// relocate from p[i] to new_p[i]
  for (int i = 10; i < 20; i++)
   new (p + i) X();
  free(p);
 }

Has this sort of thing been considered before?

Go ahead, shoot me down!

Greg.
--
Gregory Bond <gnb@bby.com.au> Burdett Buckeridge & Young Ltd Melbourne Australia
``Efforts to maintain the "purity" of a language only succeed in establishing an
  elite class of people who know the shibboleths.  Ordinary folks know better,
  even if they don't know what "shibboleth" means.'' - Larry Wall
---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy is
  in http://reality.sgi.com/employees/austern_mti/std-c++/policy.html. ]





Author: devitto@ferndown.ate.slb.com
Date: 1996/02/20
Raw View
On 10 Feb 96 08:35:39 GMT, john (j.d.) hickin (hickin@bnr.ca) wrote:
: |> Go ahead, shoot me down!

: POW!

I 'wrote' with Bjarne about a general 'renew' a couple of years ago.  u
Basically the problem is that a general "resize this C array" routine is
difficult without virtual contructors, and virtual copy constructors
- otherwise the routine would need static type binding (templates).
--
_____________________________________________________________________________
  Dom De Vitto                                 devitto@ferndown.ate.slb.com
  Schlumberger Automatic Test Equipment                      fox@bcs.org.uk
  Board Systems                          Desk/voicemail: +44(0) 1202 850951
  Wimborne, Dorset,                      Site reception: +44(0) 1202 850850
  England, BH21 7PP                      Fax:            +44(0) 1202 850988
_____________________________________________________________________________
---
[ To submit articles: try just posting with your news-reader.
                      If that fails, use mailto:std-c++@ncar.ucar.edu
  FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html
  Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu.
]





Author: tony@online.tmx.com.au (Tony Cook)
Date: 1996/02/20
Raw View
devitto@ferndown.ate.slb.com wrote:
: On 10 Feb 96 08:35:39 GMT, john (j.d.) hickin (hickin@bnr.ca) wrote:
: : |> Go ahead, shoot me down!

: : POW!

: I 'wrote' with Bjarne about a general 'renew' a couple of years ago.  u
: Basically the problem is that a general "resize this C array" routine is
: difficult without virtual contructors, and virtual copy constructors
: - otherwise the routine would need static type binding (templates).

Urr...why?

Doesn't using delete [] on a pointer with the wrong static type
produce undefined behaviour anyway? ([expr.delete] stanza 3)

Actually, if we had a memory allocator that split what realloc does
into 'attempt to expand this block' and 'allocate a new block' then
we can write renew ourselves.  Unfortunately, at least some,
operating systems, won't support this.
--
        Tony Cook - tony@online.tmx.com.au
                    100237.3425@compuserve.com
---
[ To submit articles: Try just posting with your newsreader.  If that fails,
                      use mailto:std-c++@ncar.ucar.edu
  FAQ:    http://reality.sgi.com/employees/austern_mti/std-c++/faq.html
  Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]