Decision on container design

Michel Fortin michel.fortin at michelf.com
Tue Feb 1 08:44:36 PST 2011


On 2011-02-01 11:12:13 -0500, Andrei Alexandrescu 
<SeeWebsiteForEmail at erdani.org> said:

> On 1/28/11 8:12 PM, Michel Fortin wrote:
>> On 2011-01-28 20:10:06 -0500, "Denis Koroskin" <2korden at gmail.com> said:
>> 
>>> Unfortunately, this design has big issues:
>>> 
>>> 
>>> void fill(Appender appender)
>>> {
>>> appender.put("hello");
>>> appender.put("world");
>>> }
>>> 
>>> void test()
>>> {
>>> Appender<string> appender;
>>> fill(appender); // Appender is supposed to have reference semantics
>>> assert(appender.length != 0); // fails!
>>> }
>>> 
>>> Asserting above fails because at the time you pass appender object to
>>> the fill method it isn't initialized yet (lazy initialization). As
>>> such, a null is passed, creating an instance at first appending, but
>>> the result isn't seen to the caller.
>> 
>> That's indeed a problem. I don't think it's a fatal flaw however, given
>> that the idiom already exists in AAs.
>> 
>> That said, the nice thing about my proposal is that you can easily reuse
>> the Impl to create a new container to build a new container wrapper with
>> the semantics you like with no loss of efficiency.
>> 
>> As for the case of Appender... personally in the case above I'd be
>> tempted to use Appender.Impl directly (value semantics) and make fill
>> take a 'ref'. There's no point in having an extra heap allocation,
>> especially if you're calling test() in a loop or if there's a good
>> chance fill() has nothing to append to it.
>> 
>> That's the issue with containers. The optimal semantics always change
>> depending on the use case.
> 
> Yep, yep, I found myself wrestling with the same issues. All good 
> points. On one hand containers are a target for optimization because 
> many will use them. On the other hand you'd want to have reasonably 
> simple and idiomatic code in the container implementation because you 
> want people to understand them easily and also to write their own. I 
> thought for a while of a layered approach in which you'd have both the 
> value and the sealed reference version of a container... it's just too 
> much aggravation.

But are you not just pushing the aggravation elsewhere? If I need a by 
value container for some reason (performance or semantics) I'll have to 
write my own, and likely others will write their own too.

Using classes for containers is just marginally better than making them 
by-value structs: you can use 'new' with a by-value struct if you want 
it to behave as a class-like by-reference container:

	struct Container {
		...
	}

	auto c = new Container();

The only noticeable difference from a class container is that now c is 
now a Container*.


>> Personally, I'm really concerned by the case where you have a container
>> of containers. Class semantics make things really complicated as you
>> always have to initialize everything in the container explicitly; value
>> semantics makes things semantically easier but quite inefficient as
>> moving elements inside of the outermost container implies copying the
>> containers. Making containers auto-initialize themselves on first use
>> solves the case where containers are references-types; making containers
>> capable of using move semantics solves the problem for value-type
>> containers.
> 
> Neither values nor references are perfect indeed. For example, someone 
> mentioned, hey, in STL I write set< vector<double> > and it Just 
> Works(tm). On the other hand, if you swap the two names it still seems 
> to work but it's awfully inefficient (something that may trip even 
> experienced developers).

Isn't that solved by C++0x, using move semantics in swap?

-- 
Michel Fortin
michel.fortin at michelf.com
http://michelf.com/



More information about the Digitalmars-d mailing list