return string - no double allocation

G

Gernot Frisch

Hello,

assume a simple sting class "DGStr".

Now this:
DGStr foo()
{
DGStr a("test"); // this will call "new char [..]"
return a; // this will call DGStr(a), which will again call "new char[]"
}

Is there a way to just copy the pointer, and do not delete the memory for
the return value of the string?
Know what I mean?
 
A

Alf P. Steinbach

* Gernot Frisch:
Hello,

assume a simple sting class "DGStr".

Now this:
DGStr foo()
{
DGStr a("test"); // this will call "new char [..]"
return a; // this will call DGStr(a), which will again call "new char[]"
}

Is there a way to just copy the pointer, and do not delete the memory
for the return value of the string?
Know what I mean?

Yes, you can do reference counting on the pointer. A simple implementation would
use boost::shared_ptr, but it does have some overhead. A smarter implementation
would use boost::intrusive_ptr, or equivalent code.

I'm not sure whether g++'s implementation of std::string does that, but I have
the impression that it does, at least to some limited extent.

An example implementation is available at <url:
http://alfsstringvalue.sourceforge.net/>. It also avoids doing 'new' when you
construct from a literal. :)


Cheers & hth.,

- Alf
 
G

Gernot Frisch

Yes, you can do reference counting on the pointer. A simple implementation
would use boost::shared_ptr, but it does have some overhead. A smarter
implementation would use boost::intrusive_ptr, or equivalent code.


I never used any of the above techniques. <ash on my head> Can you briefly
explain how I would implement that in my class:

static char* NO_DATA=""; // like NULL, but c_str() can return this
class DGStr
{
public:
DGStr() :m_len(0),m_dat(NO_DATA) {}
DGStr(const DGStr& s):m_len(0),m_dat(NO_DATA) {*this=s;}
DGStr& operator=(const DGStr& s)
{
Alloc(s.m_len);
m_len=s.m_len;
strcpy(m_dat, s.m_dat);
}
~DGStr() {if(m_dat!=NO_DATA) delete[] m_dat;}

const char* c_str()const {return m_dat;}

private:
// very simple non optimized code
void Alloc(int n)
{
char* pD=new char[n+1];
if(m_dat != NO_DATA)
{
memcpy(pD, m_dat, m_len+1);
delete[] m_dat;
}
m_dat = pD;
}
// data
char* m_dat;
int m_len;
}
 
A

Alf P. Steinbach

* Gernot Frisch:
Yes, you can do reference counting on the pointer. A simple
implementation would use boost::shared_ptr, but it does have some
overhead. A smarter implementation would use boost::intrusive_ptr, or
equivalent code.


I never used any of the above techniques. <ash on my head> Can you
briefly explain how I would implement that in my class:

static char* NO_DATA=""; // like NULL, but c_str() can return this
class DGStr
{
public:
DGStr() :m_len(0),m_dat(NO_DATA) {}
DGStr(const DGStr& s):m_len(0),m_dat(NO_DATA) {*this=s;}
DGStr& operator=(const DGStr& s)
{
Alloc(s.m_len);
m_len=s.m_len;
strcpy(m_dat, s.m_dat);
}
~DGStr() {if(m_dat!=NO_DATA) delete[] m_dat;}

const char* c_str()const {return m_dat;}

private:
// very simple non optimized code
void Alloc(int n)
{
char* pD=new char[n+1];
if(m_dat != NO_DATA)
{
memcpy(pD, m_dat, m_len+1);
delete[] m_dat;
}
m_dat = pD;
}
// data
char* m_dat;
int m_len;
}

No explanation because it gets real complicated real fast and I've given a link
to an implementation, and since this is a learning exercise it's important to
focus on the most important thing to learn.

Namely, to first make the code correct, and only then (if at all) optimize.

Correctness issues: you're missing a constructor to create a DGStr with other
than empty string; operator= is missing a result specification; and Alloc
performs a needless copying, presumably to handle self-assignment but that
should be handled in some different way.

Design: it's generally a bad idea to implement a copy constructor in terms of
assignment. Instead implement assignment in terms of copy construction.

Style: never use all uppercase for non-macros (except for idiomatic usage),
always use it for macros.


Cheers & hth.,

- Alf
 
A

Alan Woodland

Alf said:
* Gernot Frisch:
Hello,

assume a simple sting class "DGStr".

Now this:
DGStr foo()
{
DGStr a("test"); // this will call "new char [..]"
return a; // this will call DGStr(a), which will again call "new
char[]"
}

Is there a way to just copy the pointer, and do not delete the memory
for the return value of the string?
Know what I mean?

Yes, you can do reference counting on the pointer. A simple
implementation would use boost::shared_ptr, but it does have some
overhead. A smarter implementation would use boost::intrusive_ptr, or
equivalent code.

I'm not sure whether g++'s implementation of std::string does that, but
I have the impression that it does, at least to some limited extent.

An example implementation is available at <url:
http://alfsstringvalue.sourceforge.net/>. It also avoids doing 'new'
when you construct from a literal. :)

Wouldn't this be a good candidate for RVO with a half-decent compiler
anyway?

http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.9


Alan
 
G

Gernot Frisch

Design: it's generally a bad idea to implement a copy constructor in terms
of assignment. Instead implement assignment in terms of copy construction.

How can that be done?

DGStr operator=(const DGStr& s)
{
*this(s);
return *this;
}

so?
 
J

James Kanze

* Gernot Frisch:

[...]
No explanation because it gets real complicated real fast and
I've given a link to an implementation, and since this is a
learning exercise it's important to focus on the most
important thing to learn.

You might also suggest Scott Meyers' C++ books. Reference
counting is Item 29 in _More Effective C++_, but someone
learning would do well to familiarize themselves with all of the
items in all three of the books. (Note that reference counting
has fallen somewhat out of favor since Scott wrote the book,
because it doesn't work well in a multi-threaded environment.)
 
A

Alf P. Steinbach

* Gernot Frisch:
How can that be done?

DGStr operator=(const DGStr& s)
{
*this(s);
return *this;
}

so?

Almost. The most common is the 'swap' idiom.

DGStr& operator=( DGStr other )
{
swap( other );
return *this;
}

where 'swap' is a member function that guarantees to swap the contents of *this
with that of the specified DGStr, in constant time, without throwing.

Here the pass-by-value is intentional: the copy is constructed at the call site,
and after the swapping that copy's destructor takes care of deallocating the old
value.

std::swap comes in handy for implementing swap.


Cheers & hth.,

- Alf
 
G

Gernot Frisch

DGStr& operator=( DGStr other )
{
swap( other );
return *this;
}

Why opeator = with an object, not a reference? Or should I implement both?


DGStr& operator=(const DGStr& a)
DGStr& operator=( DGStr other )
 
S

SG

Why opeator = with an object, not a reference?

This is the copy-&-swap trick. Here, the copying is done via pass-by-
value. It's preferable in many case where swapping is cheap and
doesn't throw. Also, its exception safety is strong.
Or should I implement both?
DGStr& operator=(const DGStr& a)
DGStr& operator=( DGStr other )

That's illegal.

Cheers!
SG
 
Joined
Mar 27, 2009
Messages
8
Reaction score
0
> Is there a way to just copy the pointer, and do not delete the memory for the return value of the string?

I wouldn't use reference counting in this case. I mean - you can, but as stated above - it gets really complicated. Instead - there is a sort of trick used by the compiler (I think is standard, but you should check) exactly for these cases:

> DGStr foo()
> {
> DGStr a("test"); // this will call "new char [..]"
> return a; // this will call DGStr(a), which will again call "new char[]"

I assume you meant that this will call "operator=", because there is no reason for it to call the constructor :)
This actually doesn't always call operator=, and hence doesn't always call "new"
>}

look at the following code:
int main(){
DGStr t1;
t1=foo(); // this will call operator=, so in your case it is bad.
DGStr t2=foo(); // this will not call operator=. It will also not call DGStr(a).
}

it might seem weired, but the compiler in this case just uses the internal instance for the new one, instead of copying it.

This can be used quite easily if you also implement the "swap" function for your class to work without allocation, then you can simply do

// instead of t=foo()
{
DGStr t1=foo();
swap(t1,t);
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,161
Messages
2,570,892
Members
47,429
Latest member
JacelynKit

Latest Threads

Top