Boost logo

Boost :

From: Beman Dawes (bdawes_at_[hidden])
Date: 2005-10-17 19:47:55


"Christopher Kohlhoff" <chris_at_[hidden]> wrote in message
news:20051017052938.33920.qmail_at_web32604.mail.mud.yahoo.com...
> Hi Beman,
>
> --- Beman Dawes <bdawes_at_[hidden]> wrote:
>> There are still some void *'s in the public interface. The io_control
>>
>> helpers have data() members returning void *'s, for example.
>>
>> Any chance of wringing out the non-memory management void *'s before
>> a
>> submission? I know it sounds picky, but a lot of C++ programmers
>> object
>> strongly to void * in public interfaces for anything except the
>> rawest of
>> raw memory management.
>
> Hmm, I'm not sure. Here are the current uses of void* and their
> rationales:
>
> * buffer() and buffers() - so that arbitrary application data
> structures can be sent without an additional buffer copy.

Where did this idea come from that the only way to avoid an additional copy
is to use a void *?

Think about the iterator interfaces to Standard Library algorithms. They
traffic nicely in pointers, yet not a buffer copy or void * to be seen.

So instead of:

     ... foo( void * data, size_t size);

something like:

     template <class RandomAccessIterator>
     ... foo( RandomAccessIterator first, RandomAccessIterator last );

> * const_buffer::data() and mutable_data::data() - to get a pointer to
> pass to OS functions like send and recv.

Return a pointer to the actual type. If the user ends up casting that to
void* to use it with an old C interface, that's the user's choice. Or use
cast-style syntax:

   template <class T> T data_cast(...);

> * IO_Control_Command::data() - to get a pointer to pass to OS functions
> like ioctl or WSAIOCtl.
>
> * Socket_Option::data() - to get a pointer to be pass to OS functions
> like setsocketopt and getsockopt.

Same comment as above.

> The last 3 are similar, in that they involve getting a pointer to be
> passed to a low-level OS function. (Note that the Endpoint concept has
> a similar thing, except that it returns an implementation-defined type
> rather than void*.)

Have you considered defining one or more concepts for the low-level OS
functions, and including a trivial wrapper to supply a model of the concept
for the most common implementation of the OS functions?

> I don't want to preclude user-defined IO_Control_Command or
> Socket_Option types, so there has to be *some* way of getting a pointer
> to the data in the public interface. However I'd be very happy to
> change it if there's a better way of accomplishing the same thing.

There isn't anything wrong with functions that return pointers (or more
generally, iterators). The objection is to void * pointers breaking type
safety. If the actual type is known (or even implementation-defined) then
use it. If the actual type can't be known ahead of usage, supply the type as
a template parameter.

The above is very mom-and-apple-pie, so I expect you have already gone over
it in your mind. And there are better interface designers than me reading
this list, so others may have better ideas. But there is certainly some way
to get rid of the void *'s, and the interface will be better for doing so.

All IMO, of course.

>
>> I also wonder if the interface could be thinned without reducing
>> functionality. For example, could buffer() and buffers() be folded
>> into one function, or at least overloads with the same name.
>
> I had considered making the mutable_buffer/const_buffer classes also
> implement the Mutable_Buffers/Const_Buffers concepts, but i found it
> was confusing as to whether the begin()/end() functions applied to the
> underlying memory.
>
> What about this for an idea:
>
> - Remove the buffer() functions.
>
> - Rename buffers() to buffer().
>
> - Have a specialisation for const_buffer<1> that supports conversion to
> const_buffer.
>
> - Have a specialisation for mutable_buffer<1> that supports conversion
> to mutable_buffer and const_buffer.
>
> Then to send a single buffer you could write:
>
> sock.write(buffer(data, size));
>
> and still use the chaining to send multiple buffers:
>
> sock.write(buffer(data1, size1)(data2, size2));
>
> The conversion to the individual buffer classes should still let you
> write:
>
> std::vector<const_buffer> bufs;
> bufs.push_back(buffer(data1, size1));
> bufs.push_back(buffer(data2, size2));
> sock.write(bufs);'
>
> or:
>
> const_buffers<2> bufs = {
> buffer(data1, size1),
> buffer(data2, size2) };
> sock.write(bufs);
>
>> Likewise, could the 12
>> free read/write functions be reduced to 2 names (presumably
>> read/write) or 4
>> names (presumably read, async_read, write, async_write) via folding
>> and/or
>> overloading?. Am I the only one who feels the large number of names
>> makes the interface appear more complex than it really is?
>
> No you're not, especially after I've had to type async_read_at_least_n
> many times ;)
>
> Reducing to 4 names might be feasible. One thing I realised recently is
> that, in terms of behaviour, read() and read_n() are just special cases
> of read_at_least_n(). The same obviously applies to write*() and the
> async equivalents.

That's the kind of thought process I suggesting you apply. Since you know
the use cases far better than I do, you should be the one to make the
decision as to how to fold these special cases into one more general case.
All I can do is encourage you to make the effort.

> Could something be done by extending the Mutable_Buffers/Const_Buffers
> concepts to have a desired_minimum_transfer() member function? Bad
> name, I know, but it would return the minimum number of bytes to read
> or write from the list of buffers.
>
> It would have to be optional so that containers like std::vector can
> still meet the concept requirements. It would probably have a default
> of 0, i.e. same semantics as current asio::read() or asio::write().
>
> You could then write:
>
> read(sock, bufs); // Same as existing read()
>
> read(sock, all_of(bufs)); // Same as read_n()
>
> read(sock, at_least(bufs, 42)); // Same as read_at_least_n().
>
> I'm ready to take guidance here :)

I've become convinced that the STL half-open range is the best way to
represent a sequence, rather than the address of first element and a count.
Thus your three forms might look like:

    read( sock, first ); // read one only
    read( sock, first, last ); // read n = =last-first
    read( sock, first, min, end ); // read at least min-first, at most
end-first

I would probably eliminate the first overload unless the case of wanting to
read only one is very, very common.

Take the above with a grain of salt since I'm not familiar with the problem
domain and only commented in the first place because void *'s are such a red
flag, and because there seemed to be a lot of names for very similar
functionality.

--Beman


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