Boost logo

Boost :

Subject: Re: [boost] [uuid] Interface
From: Vladimir Batov (batov_at_[hidden])
Date: 2008-12-19 18:16:41


>>> The behavior of the default constructor that you'd like to see (apologies if I got it wrong) is to create an invalid uuid. I feel that is not the canonical use of the default constructor. Indeed, that usage of the default constructor *not* to actually create an object has become quite popular. It does not make it right though. I find it unfortunate and hackish.
>>>
>>
>> I agree. I like things to either default construct with a full usable
>> object, or to not be able to default construct it at all.
>>
>
> What defines "fully usable"? All the member functions in a nil UUID
> return reasonable, predictable results, and none throw exceptions.

Surely "predictable results" do not make a thing usable. A broken-down car behaves predictably (does not start). It even might be considered "usable" (as scrap metal or a frontyard "feature"). It is not usable from the *conventional* mainstream point of view as it fails to provide the service the car was *designed for* in the first place.

My interpretation of "fully usable" is a "valid" object, that is an object that provides the *main* functionality the class's been designed/introduced for. For a pointer it'd be a pointer pointing somewhere, for an integer, double, std::string, std::map, etc. it'd be *any* integer, double, std::string, std::map as all their values are valid (those classes just have no an "invalid" concept), i.e. int() is valid, std::string is empty but is still valid.

> Do you have some examples of other objects that do (or should) behave
> similarly to your desire for uuid?

int example = int();
double d = double();
std::string str;
in fact probably all the classes with the value semantics.

> In my view, a uuid is essentially
> a pointer (though the dereference operation is context-dependant), so
> it should be null/nil by default until you define what it should be
> pointing to.

To me uuid is a class with the *value* (rather than pointer) behavior/semantics. Value-semantics objects/classes *own* its underlying data. Pointer-semantics objects do *not*. Therefore, I feel that the conventional "language" interpretation of

    Ball ball;

is "give me a ball" that satisfies the basic properties of a ball. Then,

    Ball ball(red);

means the same (all the basic ball properties) plus the color. Asking for an unusable ripped into shreds ball is an anomaly, an exception. So, it needs to be treated as such

    Ball ball(Ball::broken());

so that the majority -- the mainstream users -- do not get confused or pay the penalty of saying "give me a ball, a real God-damn ball and not those shreds".

With pointer-semantics objects the situation is starkly different as a pointer does not house any data. The pointer's *property* is to *point* to and *share* some data. That is its distinct characteristic for which pointers deployed. With that "pointing/sharing" characteristic comes in the twist of *not* pointing anywhere. The notion of a valid pointer pointing to some data is so obvious that it is widely understood that creating a pointer without pointing it anywhere makes it invalid. That is a *distinct* specific characteristic of a pointer. Unfortunately, that interpretation (of default-initialized) is making its way into value-semantics classes for which it is far from obvious/intuitive and in fact in general terms incorrect. Default-initialized is still *initialized*. There is nothing invalid about it.

As for uuid I do not see how it can be treated as a pointer when the pointer's property is to *share* and the uuid's property is to be unique. Surely, we copy a uuid around (i.e. making several copies of the same). Surely, internally, those copies can even share the same implementation (via ref.-counting). Still, those are "technicalities". For all external purposes uuid is a value-semantics class (and the discussed class is implemented that way). Therefore, my interpretation is

uuid id; // Give me the best you (OS/infrastructure) can come up with
uuid id(some-gen); // I know better. Give me what I am asking for.
uuid id(uuid::null()); // Do not want anything but have to have this placeholder

I bet for portability purposes (and understandable unwillingness to go into every detail) the first will be used overwhelmingly. Some insist that #3 is to be used more often. I disagree as it goes against the C++ grain which states that objects are created when they are needed, i.e. *valid*. To me seeing the following

int i; // Unusable. Initialized later
int* p; // Unusable. Initialized later
uuid id; // Unusable. Initialized later

is equally worrisome regardless if an object is "uninitialized invalid" (as int) or "initialized invalid" (as uuid).

> Take, for example, a map<string, uuid>. No default constructor means
> that you can't use operator[],

Of course an empty std::map is a valid map. And you certainly can use operator[] with it. :-) But I get your point -- operator[] on an empty std::vector will bomb. It does not make that vector invalid though as a good working car is not "invalidated" by one's attempts to run it on water.

> and one that generates a random uuid
> means you lose the convenient semantics of a non-existant element
> being equivalent to the (trivially) invalid one.

1. Well, "generates a random uuid" is pretty much what I want from uuid. Therefore, I want this functionality in the most convenient form (apart from that long and boring talk about value- vs. pointer-semantics).

2. I do not see you losing anything as that non-existent is still available although in a more explicit form (which is good from the long-term maintenance point) -- uuid::null().

> (I say "trivially"
> because afaik most uses of uuid do allow you to check whether a uuid
> points to something or if it's garbage, though not terribly quickly or
> conveniently.)

You insist on "points". I insist on "has".

> I'm also not the only person that thinks ...

That does not make it right, right? George W. was not alone thinking he was the best candidate. :-)

>> default-constructed instances should be equal.

Well, I find that thought unreasonable and I bet you won't find it in the Standard.

Take something as reasonable as
>
> vector<uuid> v; v.resize(10);

I am not sure what it proves. You seem to extend the notion of the invalid uuid on to a vector of invalid uuids. I suspect "v.reserve(10)" is in order and then you'll put something meaningful into that vector when you have the data. IMHO trying to cater for incorrect usages as above enforces bad programming practices and wrong expectations.

Best,
V.


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