On Wed, 6 Nov 2002 11:52:21 -0300, "Fernando Cacciola" <[EMAIL PROTECTED]> wrote:
> >----- Original Message ----- >From: "Gennaro Prota" <[EMAIL PROTECTED]> >To: <[EMAIL PROTECTED]> >Sent: Wednesday, November 06, 2002 8:13 AM >Subject: [boost] Re: Boost Array: read/write access to entire data > > >> On Tue, 5 Nov 2002 18:22:22 -0300, "Fernando Cacciola" >> <[EMAIL PROTECTED]> wrote: >> >> >I also think it is useful for the case when you have more than one object >> >and you don't know how to perform a (no throw) move/swap of those >objects. >> >That is, if you have: >> > >> >T[0] >> >T[1] >> >... >> > >> >How do you implement no-throw swapping without a transacted approach (so >> >that you can rollback state in case of an exception)? >> > >> >The double buffer would allow you to have no-throw swap in the array even >> >if you don't have it in the objects themselves. >> >> >> Well, I'm not sure what you are trying to say. BTW I couldn't find the >> details of construct_as. Are you saying that it doesn't use the copy >> constructor? Otherwise I don't see how the tecnique achieves a >> *no-throw* swap: either the copy constructor is nothrow itself and >> thus the tecnique is not needed, or it throws and the tecnique only >> gives you the strong guarantee. Or am I missing something? >> >> Genny. >> >OK. I'll try to explain it better. It's better that I explain it better too :-) I was commenting on your phrase "would allow you to have a _no-throw_ swap". The original code I've seen is: void swap(StrongStorage& other) { using std::swap; if(other.cacheInitialized) { getOtherCache().template construct_as<value_type>(other.value()); } try { ... } catch(...) { ... } etc.. } Now, as I said, I DO NOT have the code of construct_as () because I haven't found it in any place, but if it uses copy construction (as I suppose) and if the copy may throw then that swap cannot provide the nothrow guarantee. We agree on that, I think. Now let us see other points: >The double buffer technique allows you to skip the expensive save/restore >mechanism required for the rollback of a 'traditional' >no-throw/strong-guarantee implementation. That is: >In our case, we have an array of fixed size which is not allocated in the >heap, so we can't use the address of the memory block used by the array >itself for swapping. >Schematically, this would be something like the following (showing only the >relevant parts): > >// [UNTESTED CODE BELOW] >template<class T, size_t N> >struct FixedArray >{ > FixedArray() : p_(storage_.address()) {} > > void swap(FixedArray&); > > typedef aligned_storage<sizeof(T)*N> storage_t ; > storage_t storage_ ; > T* p_ ; >} ; > >Now, let's consider a swap() with strong guarantee (not a no-throw swap, >yet) > >template<class T, size_t N> >FixedArray<T,N>::swap ( FixedArray<T,N>& rhs ) >{ > save_a_copy_of_the_current_state_without_throwing(); How do you do that? Do you use memcpy? If so you are in UB land (though it will usually work) > try > { > using std::swap ; > for ( size_t i = 0 ; i < N ; ++ i ) > swap(p_[i],rhs.p_[i]); // THIS MAY THROW This must be just an oversight. Weren't you thinking to p_ as the pointer to the array in the free store case? > } > catch(...) > { > restore_previous_state_without_throwing(); Same note, of course. > throw; > } >} > >A no-throw swap would be essentially the same except that exception possibly >thrown by the T's swap are eaten. > >Do we agree that the above is the minimun 'traditional' implementation? It depends. Don't make me say that :-) Once you are in the catch and you have swapped N/2 objects... > >Now, let's see how the double buffer could be used: > >// [UNTESTED CODE BELOW] > >template<class T, size_t N> >struct FixedArray >{ > FixedArray() : idx_(0) { >storage_initialized_[0]=storage_initialized_[1]=false;} > > void swap(FixedArray&); > > T* data() const { return storage_[idx_].address() ; } > > typedef aligned_storage<sizeof(T)*N> storage_t ; > storage_t storage_[2] ; > bool storage_initialized_[2]; > int idx_ ; >} ; > > >template<class T, size_t N> >FixedArray<T,N>::swap ( FixedArray<T,N>& rhs ) >{ > // NO NEED FOR SAVING STATE BEFORE. > try > { > // (1) Copy active storage in rhs to mirror storage here > int mirror_idx = (idx_+1)%2; Yep :-) I commented that you could have written int mirror_idx = 1 - idx; but then I saw your pre-increments below and I understood. > T* lhs_mirror = storage_[mirror_idx].address() ; > T* rhs_source = rhs.storage_[rhs.idx_].address() ; > for ( size_t i = 0 ; i < N ; ++ i ) > { > if ( storage_initialized_[mirror_idx] ) > lhs_mirror->~T(); Who says you that you can invoke the destructor on all the N elements? I think the destruction must be done at the moment you know you have constructed, say, i elements and the construction of the (i+1)-th failed. In that case you call the destructor on the first i. > new(lhs_mirror++) T(*rhs_source++); // THIS MAY THROW > } > > // (2) Copy active storage in here to mirror storage in rhs > int rhs_mirror_idx = (rhs.idx_+1)%2; > T* rhs_mirror = rhs.storage_[rhs_mirror_idx].address() ; > T* lhs_source = storage_[idx_].address() ; > for ( size_t i = 0 ; i < N ; ++ i ) > { > if ( rhs.storage_initialized_[rhs_mirror_idx] ) > rhs_mirror->~T(); > new(rhs_mirror++) T(*lhs_source++); // THIS MAY THROW > } > > // (3) Swaps active storages (commit changes) > ++ idx_ ; > ++ rhs.idx_ ; > > } > catch(...) > { > // NO NEED TO RESTORE PREVIOUS STATE. > throw; > } >} > > >So, the basic idea is essentially the same as in Anthony's code. We copy to >a mirror buffer so that if exceptions are thrown the original state is >preserved. If no exceptions are thrown, we activate the mirror buffer in >order to 'commit' the changes. I understand that. But, as you showed, if new(lhs_mirror++) T(*rhs_source++); does throw then swap throws too. Which was my point. Genny. _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost