| Thread overview | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
April 10, 2014 Design with appender: good or bad? | ||||
|---|---|---|---|---|
| ||||
Are there any drawbacks with this design, i.e. using buf.put() here
(instead of a less efficient items ~= item;)?
struct MyStruct {
Appender!(string[]) buf;
string name;
this(string name) {
this.name = name;
buf = appender!(string[]);
}
public addItem(string item) {
buf.put(item);
}
@property string[] items() {
return buf.data;
}
}
| ||||
April 10, 2014 Re: Design with appender: good or bad? | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Chris | On Thursday, 10 April 2014 at 09:47:35 UTC, Chris wrote:
> Are there any drawbacks with this design, i.e. using buf.put() here
> (instead of a less efficient items ~= item;)?
>
> struct MyStruct {
> Appender!(string[]) buf;
> string name;
>
> this(string name) {
> this.name = name;
> buf = appender!(string[]);
> }
>
> public addItem(string item) {
> buf.put(item);
> }
>
> @property string[] items() {
> return buf.data;
> }
> }
Appender supports ~ and ~=, so you can have your cake and eat it.
| |||
April 10, 2014 Re: Design with appender: good or bad? | ||||
|---|---|---|---|---|
| ||||
Posted in reply to John Colvin | On Thursday, 10 April 2014 at 10:00:16 UTC, John Colvin wrote:
> On Thursday, 10 April 2014 at 09:47:35 UTC, Chris wrote:
>> Are there any drawbacks with this design, i.e. using buf.put() here
>> (instead of a less efficient items ~= item;)?
>>
>> struct MyStruct {
>> Appender!(string[]) buf;
>> string name;
>>
>> this(string name) {
>> this.name = name;
>> buf = appender!(string[]);
>> }
>>
>> public addItem(string item) {
>> buf.put(item);
>> }
>>
>> @property string[] items() {
>> return buf.data;
>> }
>> }
>
> Appender supports ~ and ~=, so you can have your cake and eat it.
Ah, this one \me (= escaped me). Sorry I wasn't precise, I was wondering if it is in any way dangerous to have
@property string[] items() {
return buf.data;
}
instead of:
string[] items;
// ...
public addItem(string item) {
items ~= item;
}
because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later.
| |||
April 10, 2014 Re: Design with appender: good or bad? | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Chris | On Thursday, 10 April 2014 at 10:16:43 UTC, Chris wrote:
> Ah, this one \me (= escaped me). Sorry I wasn't precise, I was wondering if it is in any way dangerous to have
>
> @property string[] items() {
> return buf.data;
> }
>
> instead of:
>
> string[] items;
> // ...
> public addItem(string item) {
> items ~= item;
> }
>
> because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later.
The only thing I know of is if you store the reference returned by items(), clear the appender, and add new items, the contents of the previously returned slice will change too. Clearing an appender is similar to setting slice length to 0 and calling assumeSafeAppend on it.
| |||
April 10, 2014 Re: Design with appender: good or bad? | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Chris | On Thursday, 10 April 2014 at 10:16:43 UTC, Chris wrote:
> because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later.
Why don't you implement "toString" on your type?
void toString(scope void delegate(const(char)[]) sink)
{
import std.format;
formattedWrite(sink, `%s`, "MyStruct(");
formattedWrite(sink, `"%s"`, name);
formattedWrite(sink, `%s`, ", ");
formattedWrite(sink, `%s`, buf.data);
formattedWrite(sink, `%s`, ")");
}
| |||
April 10, 2014 Re: Design with appender: good or bad? | ||||
|---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | On Thursday, 10 April 2014 at 11:40:46 UTC, monarch_dodra wrote:
> On Thursday, 10 April 2014 at 10:16:43 UTC, Chris wrote:
>> because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later.
>
> Why don't you implement "toString" on your type?
>
> void toString(scope void delegate(const(char)[]) sink)
> {
> import std.format;
> formattedWrite(sink, `%s`, "MyStruct(");
> formattedWrite(sink, `"%s"`, name);
> formattedWrite(sink, `%s`, ", ");
> formattedWrite(sink, `%s`, buf.data);
> formattedWrite(sink, `%s`, ")");
> }
Thanks. But the question was not about how to print it to console, but whether there are any hidden dangers in using Appender in this way, like the one Rene mentioned.
| |||
April 10, 2014 Re: Design with appender: good or bad? | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Chris | On Thursday, 10 April 2014 at 13:29:39 UTC, Chris wrote:
> Thanks. But the question was not about how to print it to console, but whether there are any hidden dangers in using Appender in this way, like the one Rene mentioned.
AFAIK, no. In fact, arguably, it's saf*er*, since an appender has a "true" reference semantic, whereas a slice has "half reference semantics": If you modify an *item* all instances will see it, but if you *add* some items, only 1 will see it.
But I guess it kind of depends on what you want.
EG:
//----
struct S1
{
Appender!(string[]) buf;
string name;
this(string name) {
this.name = name;
buf = appender!(string[]);
}
public void addItem (string item) @property
{
buf.put(item);
}
@property string[] items()
{
return buf.data;
}
}
struct S2
{
string[] items;
string name;
this(string name) {
this.name = name;
}
public void addItem (string item) @property
{
items ~= item;
}
}
void main()
{
foreach ( S ; TypeTuple!(S1, S2) )
{
auto bob = S("Bob");
bob.addItem("PS4");
auto bob2 = bob;
bob2.addItem("XBOXONE");
writefln("%s.bob %s", S.stringof, bob.items);
writefln("%s.bob2 %s", S.stringof, bob2.items);
}
}
//----
S1.bob ["PS4", "XBOXONE"]
S1.bob2 ["PS4", "XBOXONE"]
S2.bob ["PS4"]
S2.bob2 ["PS4", "XBOXONE"]
//----
That said, it feels like you are using Appender like a container. Maybe "Array" is a better fit?
| |||
April 10, 2014 Re: Design with appender: good or bad? | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Rene Zwanenburg | On Thursday, 10 April 2014 at 10:47:08 UTC, Rene Zwanenburg wrote: > On Thursday, 10 April 2014 at 10:16:43 UTC, Chris wrote: >> Ah, this one \me (= escaped me). Sorry I wasn't precise, I was wondering if it is in any way dangerous to have >> >> @property string[] items() { >> return buf.data; >> } >> >> instead of: >> >> string[] items; >> // ... >> public addItem(string item) { >> items ~= item; >> } >> >> because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later. > > The only thing I know of is if you store the reference returned by items(), clear the appender, and add new items, the contents of the previously returned slice will change too. Clearing an appender is similar to setting slice length to 0 and calling assumeSafeAppend on it. Like so: import std.stdio, std.array; void main() { auto mystr1 = MyStruct1("Hello1"); auto mystr2 = MyStruct2("Hello2"); mystr1.addItem("World1"); mystr2.addItem("World2"); auto data1 = mystr1.items; writeln(mystr1.buf.data.length); auto data2 = mystr2.items; writeln(data1[0]); writeln(data2[0]); mystr1.clear(); mystr2.clear(); writeln(mystr1.buf.data.length); writeln(data1[0]); writeln(data2[0]); mystr1.addItem("After World 1"); mystr2.addItem("After World 2"); writeln(mystr1.items[0]); writeln(mystr2.items[0]); writeln(data1[0]); // Is now After World 1 writeln(data2[0]); // Still is World2 } struct MyStruct1 { Appender!(string[]) buf; string name; this(string name) { this.name = name; buf = appender!(string[]); } public void addItem(string item) { buf.put(item); } @property ref auto items() { return buf.data; } public void clear() { buf.clear(); } } struct MyStruct2 { string[] stuff; string name; this(string name) { this.name = name; } public void addItem(string item) { stuff ~= item; } @property ref string[] items() { return stuff; } public void clear() { stuff.clear(); } } | |||
April 10, 2014 Re: Design with appender: good or bad? | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Chris | On Thursday, 10 April 2014 at 13:53:46 UTC, Chris wrote:
> On Thursday, 10 April 2014 at 10:47:08 UTC, Rene Zwanenburg wrote:
>> On Thursday, 10 April 2014 at 10:16:43 UTC, Chris wrote:
>>> Ah, this one \me (= escaped me). Sorry I wasn't precise, I was wondering if it is in any way dangerous to have
>>>
>>> @property string[] items() {
>>> return buf.data;
>>> }
>>>
>>> instead of:
>>>
>>> string[] items;
>>> // ...
>>> public addItem(string item) {
>>> items ~= item;
>>> }
>>>
>>> because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later.
>>
>> The only thing I know of is if you store the reference returned by items(), clear the appender, and add new items, the contents of the previously returned slice will change too. Clearing an appender is similar to setting slice length to 0 and calling assumeSafeAppend on it.
>
> Like so:
>
> import std.stdio, std.array;
>
> void main() {
> auto mystr1 = MyStruct1("Hello1");
> auto mystr2 = MyStruct2("Hello2");
> mystr1.addItem("World1");
> mystr2.addItem("World2");
> auto data1 = mystr1.items;
> writeln(mystr1.buf.data.length);
> auto data2 = mystr2.items;
> writeln(data1[0]);
> writeln(data2[0]);
> mystr1.clear();
> mystr2.clear();
> writeln(mystr1.buf.data.length);
> writeln(data1[0]);
> writeln(data2[0]);
> mystr1.addItem("After World 1");
> mystr2.addItem("After World 2");
> writeln(mystr1.items[0]);
> writeln(mystr2.items[0]);
> writeln(data1[0]); // Is now After World 1
> writeln(data2[0]); // Still is World2
> }
>
> struct MyStruct1 {
> Appender!(string[]) buf;
> string name;
>
> this(string name) {
> this.name = name;
> buf = appender!(string[]);
> }
>
> public void addItem(string item) {
> buf.put(item);
> }
>
> @property ref auto items() {
> return buf.data;
> }
>
> public void clear() {
> buf.clear();
> }
> }
>
> struct MyStruct2 {
> string[] stuff;
> string name;
>
> this(string name) {
> this.name = name;
> }
>
> public void addItem(string item) {
> stuff ~= item;
> }
>
> @property ref string[] items() {
> return stuff;
> }
>
> public void clear() {
> stuff.clear();
> }
> }
The funny thing, though, is that after clearing buf
writeln(data1[0]);
still prints "World1". It only changes after I add a new item to buf.
| |||
April 10, 2014 Re: Design with appender: good or bad? | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Chris | On Thursday, 10 April 2014 at 13:57:26 UTC, Chris wrote:
>
> The funny thing, though, is that after clearing buf
>
> writeln(data1[0]);
>
> still prints "World1". It only changes after I add a new item to buf.
Correct. The memory has not been touched by clear(), it just sets it's internal used elements counter to 0. When adding new items the old ones will be overwritten.
| |||
Copyright © 1999-2021 by the D Language Foundation
Permalink
Reply