Boost logo

Boost :

Subject: Re: [boost] std::map::find() wrapper
From: Marsh Ray (marsh_at_[hidden])
Date: 2011-05-04 19:01:09


On 05/04/2011 02:14 PM, Nevin Liber wrote:
> On 4 May 2011 13:42, Marsh Ray<marsh_at_[hidden]> wrote:
>>
>> Because it uses the C++ type system to restrict the number of possible
>> interpretations about the valid operations on the returned value, thus
>> communicating information that would otherwise go unspecified.
>
> If, for instance, you pass a shared_ptr, you are saying that the callee is
> allowed to be involved with ownership.

We could say that the interface is defined in such a way that the
programming language does not prohibit it, even though the interface
could have been defined that way.

But that's still not the same as saying it's allowed.

Consider intrusive_ptr. In 'intruded pointeee' classes it's ususally
possible to construct a valid intrusive_ptr from just a raw pointer.
Following your maximally-allowed logic, it would be impossible to refer
by any method to an 'intruded pointeee' class instance without the
callee being "involved with ownership". Even an object constructed on
the stack.

> This is functionality I typically
> don't want to grant willy-nilly to every function I call.

Then don't pass them as shared_ptrs. If you know it's non-null, deref it
and pass a reference, otherwise pass an optional<>.

> If you want to use the C++ type system to enforce this kind of thing, create
> new types to wrap the raw pointers; don't reuse things like shared_ptr for
> this.

Agreed.

> (And if you are creating new types, then good for you, and I remove
> my objection. But it isn't typical, at least in my experience.)

Look again... how many variations on smart pointers and reference
wrapper have been invented over the years?

I put together one when I needed it the other day based on the
"clone_ptrs" designs that others have proposed.

>> Sometimes this lets the compiler help spot problems with the program's
>> correctness.
>
> Could you elaborate? Again, we are talking about pointers not involved with
> ownership.

OK.

So to the C++ compiler, pointers are pointers very much like those in C.
There's no concept of some pointers "involved with ownership" and others
"not involved with ownership". These terms were initially coined by
developers in order to talk about why they had so many memory leaks and
use-after-free bugs in their program. These terms are great in source
code comments when you have to use pointers.

Then people realized that C++ had templates and deterministic
destructors and could support this nifty technique RAII which could use
the familiar scoped lifetime rules to help manage "ownership" and
lifetime issues of dynamically allocated objects. So we got smart
pointers particularly to help with that one aspect of program correctness.

But C++ has a much more powerful statically-checked type system. It
supports abstraction and provides a logic programming system based on
template instantiation. This has the ability to do a great many useful
things, but most of all it assists us in proving the correctness of our
programs.
http://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence

However, it can only be effective to the degree to which we use it,
which primarily means defining _restrictions_ on the expressions in
which objects of types are allowed to participate.

Interestingly, this is in some ways the exact opposite of what the OO
paradigm does with behavioral subtyping through inheritance. The Liskov
substitution principle states "if S is a subtype of T, then objects of
type T may be replaced with objects of type S" (wikipedia). Note that
that's a positive statement.

Consider the 'const' qualifier for example. A 'const T' is not a
behavioral subtype of T because you can't use const Ts in many places
where a T is required. If you tried to make T a subtype of 'const T',
then the subtype could no longer implement the behavior that it always
stays the same.

Quite often, the concepts people want to express about types do not fit
cleanly into either box. The valid actions for one type are often a
merely overlapping set with of those of another. Sorting all this out
gets maddeningly complicated and most other languages seem to have given
up and imposed severe restrictions on multiple inheritance and their
template system.

Still, most of us find the ability to define restrictions on our types
very useful and the zeal with which it is pursued is one of the defining
characteristics of the Boost project (and C++ in general). People wrote
code without regard to 'const' for a long time. It wasn't an effortless
transition, but now that the dust has settled we try to write
const-correct code whenever practical.

Again, the general principle is that we should refine the types we use
in our programs as precisely as practical. They should be abstract where
possible in support of generic programming and restrictive in support of
compile-time correctness checking. Of course, this often introduces
additional complexity and the trade-off is best determined by the needs
of the situation and the developer's good taste.

Off the top of my head, here are some things that pointers can do that
optional<T> or optional<&T> can not:

    pointer arithmetic
    operator []
    exist in an undefined not-default-constructed state
    operator delete (i.e., raise questions of ownership)
    conversion to base * and void * types
    reinterpretation as uintptr_t
    passing to extern "C" API functions

I don't see anything that optional<> can do that a pointer cannot do,
except perhaps some methods of explicit construction that are probably
more efficient than their equivalents for pointers (efficiency is often
a good by-product of the type information too).

Thus, it is better to use optional<T or &> instead of pointers whenever
it supplies the required operations.

- Marsh


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