Re: gcc and a problem with temporaries
Re: gcc and a problem with temporaries
- Subject: Re: gcc and a problem with temporaries
- From: Howard Hinnant <email@hidden>
- Date: Mon, 5 May 2008 12:05:16 -0400
On Apr 30, 2008, at 4:51 PM, Scott Thompson wrote:
I've run into an interesting problem while compiling some legacy
code with gcc (code that is to be made cross-platform). The
solution is perplexing me so I thought I would ask here to see if
anyone had ideas. I've boiled the problematic code down to this
small sample:
class Owned
{
public:
Owned() {};
};
class Inited
{
public:
Inited();
Inited(Owned *owned);
Inited &operator=(Owned* itemToAssign);
// Problematic Delcaration
Inited(Inited &itemToCopy);
};
Owned *makeOwned()
{
return new Owned;
}
int main (int argc, char * const argv[]) {
/* ** Compile error here ** */
Inited myInited = makeOwned();
}
The compile error is:
main.cpp:25: error: no matching function for call to
'Inited::Inited(Inited)'
main.cpp:15: note: candidates are: Inited::Inited(Inited&)
main.cpp:11: note: Inited::Inited(Owned*)
main.cpp:25: error: initializing temporary from result of
'Inited::Inited(Owned*)'
So it would appear that GCC is trying to create a temporary and then
initialize the "myInited" from that temporary. I gather that would
be roughly equivalent to:
const Inited t1(makeOwned()); Inited myInited(t1);
Because the temporary is const, it can't use the copy constructor
with the non-const parameter. But, apparently, the presence of this
constructor is what causes it to create the temporary in the first
place. If I remove this copy constructor then everything compiles
without trouble.
I can also fix the problem by declaring:
Inited(const Inited &itemToCopy); /* note the addition of a
const */
While adding that that makes sense for this toy example, it doesn't
work in the original code where "Inited" is a "smart pointer"
object. The "itemToCopy" is expected to relinquish control of the
pointer it contains to the newly initialized object and is,
therefore, not const in this context.
I could also change the line of code where the error is reported to:
Inited myInited(makeOwned());
Again in the toy code this is not really a problem, but the original
code base uses the version with the equals sign "all over the
place". That code passes through several other C++ compilers
without issue, but it won't compile through XCode.
Right now, it looks like a potential solution would be to add the
const copy constructor, and make the embedded pointer of the smart
pointer class "mutable". But that seems a bit kludgey.
Can anyone suggest a more elegant solution?
You have stumbled into the domain of "move semantics". :-) This is
something that will be much better supported in the next C++ standard
(which is not yet available). However you can emulate this support
today. It won't be as good as C++0X, but it will be close, and it get
you headed in the right direction for future maintenance.
In a nutshell, a move is nothing more than a shallow copy, followed by
stripping the source of its resources (such as an owned pointer). And
it is always safe to move from rvalues (temporaries). It is never
safe to move from lvalues, unless you use syntax that doesn't look
like a copy. Your class Inited is what will be referred to as a "move-
only" class. That is, you can move from it, but you can't copy from
it. Once designed correctly with respect to this framework (which is
not too hard today, and downright easy in C++0X), your main() will
compile just as you have it.
Here's how you can fake it today:
class Owned
{
public:
Owned() {};
};
class Inited
{
Owned* owned_;
public:
Inited();
Inited(Owned *owned) : owned_(owned) {}
~Inited() {reset();}
Inited &operator=(Owned* itemToAssign)
{
reset();
owned_ = itemToAssign;
return *this;
}
// move semantics
Inited(const Inited &itemToCopy)
: owned_(const_cast<Inited&>(itemToCopy).release())
{}
Inited& operator=(const Inited &itemToCopy)
{
if (this != &itemToCopy)
{
reset();
owned_ = const_cast<Inited&>(itemToCopy).release();
}
return *this;
}
void reset()
{
delete owned_;
owned_ = 0;
}
Owned* release()
{
Owned* t = owned_;
owned_ = 0;
return t;
}
private:
// disable copy semantics
Inited(Inited&);
Inited& operator=(Inited&);
};
inline
Inited
move(Inited& p)
{
return Inited(p.release());
}
And here is an example main():
int main () {
Inited myInited = makeOwned();
// Inited i2 = myInited; // purposefully won't compile
Inited i2 = move(myInited); // ok, i2 owns it and myInited doesn't
}
In C++0X, the std::lib will supply the move function so you won't have
to write it (you'll need to call std::move instead of your hand-
written move though).
And your Inited class will change to:
class Inited
{
Owned* owned_;
public:
Inited();
Inited(Owned *owned) : owned_(owned) {}
~Inited() {reset();}
Inited &operator=(Owned* itemToAssign)
{
reset();
owned_ = itemToAssign;
return *this;
}
// move semantics
Inited(Inited&& itemToCopy)
: owned_(itemToCopy.release())
{}
Inited& operator=(Inited&& itemToCopy)
{
reset();
owned_ = itemToCopy.release();
return *this;
}
// disable copy semantics
Inited(const Inited&) = delete;
Inited& operator=(const Inited&) = delete;
void reset()
{
delete owned_;
owned_ = 0;
}
Owned* release()
{
Owned* t = owned_;
owned_ = 0;
return t;
}
};
The "&&" is called a "rvalue reference", and attracts rvalue arguments
(temporaries). lvalues will be attracted to the "normal" copy ctor
and assignment, and will subsequently fail to compile (as desired).
This is all there really is, but if you would like to read more about
it, there's a brief intro here:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html
Hope this helps.
-Howard
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Xcode-users mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden