Ruling out arbitrary cost copy construction?

Sean Kelly sean at invisibleduck.org
Thu Nov 4 15:00:34 PDT 2010


Andrei Alexandrescu Wrote:

> On 10/6/10 11:34 AM, Andrei Alexandrescu wrote:
...
> >
> > Until now I think we're solid on design decisions. The discussion starts
> > here.
> >
> > And then there was this nagging thing which is well-understood in the
> > C++ world. A sealed range returns by value and accepts by value. But in
> > C++, an object's copy constructor is arbitrarily expensive. The library
> > must be designed in accordance to that and carefully navigate around
> > that issue.
> >
> > For example, swap is a fundamental primitive used in many algorithms. It
> > should swap objects at constant cost. Indeed, for references, swap is
> > easy to implement:
> >
> > void swap(T)(ref T lhs, ref T rhs)
> > {
> > assert(!pointsTo(lhs, rhs) && !pointsTo(rhs, lhs)
> > && !pointsTo(lhs, lhs) && !pointsTo(rhs, rhs));
> > T tmp = move(lhs);
> > lhs = move(rhs);
> > rhs = move(tmp);
> > }
> >
> > or similar. However, a sealed range does not offer references, so trying
> > e.g.
> >
> > swap(r1.front, r2.front);
> >
> > will not work. This is a problem.


I probably missed it, but I imagine there was discussion of sealed ranges returning a wrapper struct for front() that's implicitly convertible to the underlying type?  The a specialization of swap could do what's needed.

 
> Thanks to all for a very illuminating discussion. The dialog revealed 
> that the topic of cheap copy construction is oddly linked with the topic 
> of garbage collection: garbage collection calls destructors concurrently 
> with other code, which means reference count code must be interlocked, 
> which makes it not-so-cheap.
> 
> This is only a manifestation of a deeper problem: destructors can 
> execute arbitrary code, and, if ran in an arbitrary thread, can cause 
> all sorts of deadlocks and essentially render "shared" inoperant.
> 
> Walter and I discussed two possibilities this morning:
> 
> 1. Never call class destructors (or never call them if more than one 
> thread is running)
> 
> 2. Each destructor should be called in the thread that created the object.
> 
> The second solution is quite attractive. It can be implemented by 
> associating each thread with a worklist - a list of objects to destroy. 
> The GC can run in any thread but it will not call destructors. Instead, 
> it adds objects to be destroyed to the threads that created them (this 
> means we need to add a field to each object with the thread ID of the 
> creating thread). When client code calls new, the allocation routine 
> will first inspect the worklist and call the destructors if needed.

This should only be necessary for non-shared instances.  Which, admittedly, are the majority of allocated objects.  So either an optional ID per memory block or instead per-thread pools to allocate from.  The ID approach would be easier to implement with the current GC though.  The same would be required for dynamically allocated structs once the GC bug has been fixed where they aren't finalized right now (or a proclamation were made that dynamic structs won't be finalized by the GC).


More information about the Digitalmars-d mailing list