|
Boost : |
From: Gennaro Prota (gennaro_prota_at_[hidden])
Date: 2004-04-13 04:17:41
On Sat, 10 Apr 2004 10:34:49 -0400, David Abrahams
<dave_at_[hidden]> wrote:
>Gennaro Prota <gennaro_prota_at_[hidden]> writes:
>
>> Hi everybody,
>>
>> as I have mentioned in some post here, a new version of
>> dynamic_bitset is in the sandbox.
>
>Neat! I just noticed that the old one is missing an empty() member
>function.
Yes. I noticed that but was not sure whether adding it or not (just to
avoid cluttering the interface with too many "utility" functions).
I've added it now, with tests (wow :)).
>> I've finished everything 4/5 days ago (including new and more tests)
>> and thought to move it to the main repository, as Jeremy asked too.
>
>What does your change do?
Hmm. Here's an extremely condensed (not exhaustive) summary of
changes, future directions and open issues. Sorry if it's terse and
incomplete, but I don't have time right now. Don't be afraid for the
number of changes though; the tests pass with at least 7 compilers,
and there are more tests than before, so we shouldn't have big
problems/regressions.
Exception Safety
All exception safety guarantees are now documented (I haven't
committed the docs yet, but the important functions have a
comment that says what guarantee they offer - the html file is
almost done). Where possible, such as for append(begin, end),
the strong guarantee is provided.
Implementation
Everything is now implemented in terms of std::vector<>.
This has quite simplified the code, and made more
evident, from reading, the exception guarantees of each
member function, as they immediately follow from the
corresponding std::vector<>'s ones.
Most functions have been rewritten, either to fix
errors or to improve efficiency; or to satisfy
exception guarantees.
The nested class reference has been reimplemented.
Formerly it stored an index and a pointer to the bitset:
besides being inefficient, that meant that swapping
bitsets either made references invalid or changed the
element they were referring to. Both problems are now
solved by storing a reference into a block and a
pre-computed mask. I've also added a namespace scope
swap (It's in namespace boost. Ok?)
NOTE: for now a reference to a bit remains valid if an
exception is thrown by a strong-guarantee operation and,
as said, it is stable after a swap(). However rules
for invalidation are not documented. If I document them
I would go for rules modeled after std::deque, rather
than std::vector, so that we leave open the door to
implement everything in terms of std::deque. Anyway, I
see this more as a future issue: if Jeremy's new iterator
categories will get into the standard dynamic_bitset could
have iterators; for now, instead, I don't feel the need
to have precise invalidation rules for references to bits.
The function count() is completely portable, including
to platforms that have padding bits in built-in integer types.
Conversions from and to string (i.e.: from_string, to_string
a constructor, and the undocumented dump_to_string) are
internationalized (but see below).
A missing parameter in the constructor from basic_string has
been added (however see below)
Stream operators are internationalized and take into account
exception masks on the stream. That's exercised by the tests,
in dyn_bitset_unit_tests4. They don't use std::basic_string
as an intermediary. (The special versions for old libstdc++
also don't use string as intermediary, but of course they
don't know about exception masks etc.)
The functions to_block_range and from_block_range have been
fixed (but see below)
Tests
There are much more tests. I can't post a detailed list
now. Will do as soon as possible.
(Some) Future directions
- deprecating conversions from and to string
Conversions to and from string are a design error of std::bitset
that we have transposed into boost. dynamic_bitset has nothing
to do with strings. If one needs a "textual" representation of
the data stored in a bitset object there are the stream operators,
which are the standard C++ convention for formatting data. By
using << and >> you have a neat separation of concerns between the
class that stores the data (dynamic_bitset in this case) and the one
that stores their textual representation (basic_string), a generic
name, operator>> or operator <<, for the free function that connects
them (which is important for template programming) and none of the
two classes encumbered with knowledge of the other. Also, you can
easily deal with locale-related issues.
- making reference private (and inhibit copying?)
(Some) Open Issues:
- Semantics of from_block_range should be clarified for the
case where distance(first, last) == b.numblocks() *but*
b.size() < (bits_per_block * b.num_blocks()). Example:
say bits_per_block = 8, b.size() = 9 and you copy two
blocks into b; should the bitset size become 16 or not?
I think there are three possibilities:
- make this case undefined behavior
- enlarging the bitset as needed:
// UNTESTED!
from_block_range(BlockIterator first, BlockIterator last,
dynamic_bitset<B, A>& result)
{
// PRE: distance(first, last) <= numblocks()
//
if ( m_bits.end() ==
std::copy (first, last, result.m_bits.begin())
)
m_num_bits = bits_per_block * b.num_blocks();
}
- ignoring additional bits
...
std::copy (first, last, result.m_bits.begin());
result.m_zero_unused_bits();
My doubt with this last case concerns input iterators,
because once you have consumed the last element it could be
lost for ever (not sure if this is a problem or not)
- the copy assignment operator currently provides the strong
guarantee:
operator=(const dynamic_bitset<Block, Allocator>& b)
{
dynamic_bitset<Block, Allocator> tmp(b);
this->swap(tmp);
return *this;
}
Should it? I lean towards 'no'.
- Should reserve() and capacity() be added? I think not,
because std::deque doesn't have them. So if you add them
to the dynamic_bitset interface then you can't implement
it in terms of deque (that's why I have removed the two
functions, after having initially implemented them)
- should std::swap be specialized/overloaded for dynamic_bitsets?
- The implementation of operator <, > and all comparison operators
require the compared bitsets to have the same size, but that's
not documented. The docs simply say that the lexicographical order
is considered, and that suggests, for instance, that 10 is < 101.
- The docs for to_block_range say:
"The type BlockOutputIterator must be a model of Output Iterator
and its value_type must be the same type as Block."
But output iterators have no notion of "value type".
iterator_traits<OutputIterator>::value_type is defined as void.
- Imitating std::bitset, the function to_ulong checks whether
there's any 1 bit beyond the positions representable in an
unsigned long, eventually throwing an overflow_error. That
goes against the "don't pay for what you don't use" principle
(think for instance if your bitset has 20 000 elements and you
are sure that only the first 8 can be set). It's also the only
place where we still check "preconditions" with exceptions and
the only reason why dynamic_bitset.hpp still needs to include
<stdexcept>.
Genny.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk