
On 05/04/2011 02:14 PM, Nevin Liber wrote:
On 4 May 2011 13:42, Marsh Ray<marsh@extendedsubset.com> 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