Yeah, in retrospect I probably shouldn't have approved the article.
+ If the assignment operator looks like
+
+ MyClass MyClass::operator = (const MyClass &other) {
+ ~MyClass();
+ new (this) MyClass(other);
+ return *this;
+ }
+
+ then it shouldn't. That approach isn't exception safe, and it
+ messes with the notion of object lifetimes in a way which is
+ likely to make Herb Sutter feel unwell if he reads this post.
Forget Herb -- that's giving me the screaming heebee jeebies! Won't using
'this' in a placement-new expression do horrible things to memory layout?
Apart from being exception-unsafe, I would expect some pointer errors
at runtime.
Has that ever been used before?
Luck++;
Phil
--
pedwards at disaster dot jaj dot com | pme at sources dot redhat dot com
devphil at several other less interesting addresses in various dot domains
The gods do not protect fools. Fools are protected by more capable fools.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
> James Dennett <ja...@evtechnology.com> wrote:
> > Peter Niessen wrote:
> > > do I need an assignment operator for a class if I have a copy
> > > constructor? Do I need a copy constructor if I have an
> > > assignment operator? My gcc version 2.95.2 19991024 on HP-UX
> > > goes into infinite loops if I declare both like
> > > MyClass (const MyClass &OldMyClass);
> > > MyClass operator = (const MyClass &OldMyClass);
> > > and it seems that is tries to do a copy in the parameter list
> > > although it's declared with &. Thus the program ends up in an
> > > infinite loop when doing
> > > MyClass mc1;
> > > MyClass mc2 (mc1);
> > > in the main ().
> > If the assignment operator looks like
> > MyClass MyClass::operator = (const MyClass &other) {
> > ~MyClass();
He means: this->~MyClass() ;
As written, the code creates a temporary MyClass, then attempts to
apply the ~ operator on it.
> > new (this) MyClass(other);
> > return *this;
> > }
> > then it shouldn't. That approach isn't exception safe, and it
> > messes with the notion of object lifetimes in a way which is
> > likely to make Herb Sutter feel unwell if he reads this post.
> Forget Herb -- that's giving me the screaming heebee jeebies! Won't
> using 'this' in a placement-new expression do horrible things to
> memory layout? Apart from being exception-unsafe, I would expect
> some pointer errors at runtime.
This idiom will cause problems if the copy constructor throws. It
isn't thread-safe (but then, operator= often isn't). Most of all, if
someone derives from MyClass and doesn't use it, the results will
probably be undefined behavior of the worst sort.
But other than that, it works. Before exceptions and multi-threading
became prevalent, I even recommended it for closed hierarchies. It is
a particularly elegant solution for avoiding multiple assignment of a
virtual base class. In the end, Scott Meyers convinced me that I was
wrong about this. I stopped recommending it because closed
hierarchies generally end up becoming open.
> Has that ever been used before?
Lots. If Deja News when back 6 or 7 years, you could find postings
from me recommending it.
--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
Unfortunately, the C++ standard uses code like this in examples for its
discussion of object lifetimes. That has led people to believe that this
is good coding practice, despite its implications for derived classes.
(Think about what the compiler-generated assignment operator for a
derived class ends up doing...)
--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Contributing Editor, C/C++ Users Journal (http://www.cuj.com)
James Kanze <James...@dresdner-bank.com> wrote in message
news:3A36151F...@dresdner-bank.com...
> > Original author wrote [or intended to write]:
> >
> > > MyClass MyClass::operator = (const MyClass &other) {
> > > this->~MyClass() ;
> > > new (this) MyClass(other);
> > > return *this;
> > > }
>
> Most of all, if
> someone derives from MyClass and doesn't use it, the results will
> probably be undefined behavior of the worst sort.
Just make MyClass MyClass::operator = (const MyClass &) virtual.
Then the derived class can easily avoid the "undefined behaviour of
the worst sort".
Cheers!
- Risto -
If the attributions are correct, then it was Peter Niessen who wrote:
>> > If the assignment operator looks like
>
>> > MyClass MyClass::operator = (const MyClass &other) {
>> > ~MyClass();
>> > new (this) MyClass(other);
>> > return *this;
>> > }
>
>> > then it shouldn't. That approach isn't exception safe, and it
>> > messes with the notion of object lifetimes in a way which is
>> > likely to make Herb Sutter feel unwell if he reads this post.
Since you single me out, I suppose it's likely because of either my November
2000 CUJ column, or Item 41 in Exceptional C++ which is devoted entirely to
debunking this anti-idiom.
If you haven't read Item 41 yet, please do. As I point out in that Item,
this approach has lots of problems, especially as written above: 1. As
commonly implemented, it can slice objects. 2. It's not exception-safe (as
you noted). 3. It plays havoc with normal object lifetimes (as you noted).
4. It makes life hellish for derived classes. 5. As written above, it's not
safe for self-assignment; the self-assignment test is mandatory (and, alas,
insufficient because of the other problems).
In the end, the main point Item 41 makes is that it's indeed okay to
implement copy assignment in terms of copy construction -- but this
anti-idiom is NOT the way to do it. Instead, used the common true idiom:
T& T::operator=( const T& other )
{
T temp( other );
Swap( temp ); // Swap is a nothrow function
return *this;
}
See Item 41 for details and discussion. Interestingly, a few people have
told me that they felt I spent too much space protesting this anti-idiom
when it's clearly wrong and not worth the thorough spanking I give it; my
response to them has been that it's needed because the anti-idiom keeps
cropping up in the newsgroups. :-) Don't feel bad, though, you're not alone
and even some experts encouraged it in the early days.
Sometime later, James Kanze <James...@dresdner-bank.com> responded in
part:
>This idiom will cause problems if the copy constructor throws. It
>isn't thread-safe (but then, operator= often isn't). Most of all, if
>someone derives from MyClass and doesn't use it, the results will
>probably be undefined behavior of the worst sort.
>
>But other than that, it works.
Well, I like the way you put that, because such heavily-caveated "damning
with faint praise" speaks volumes. :-)
Even so, I really really recommend against even such slight encouragement of
this anti-idiom. It's fragile and fraught with peril. For example, you're an
expert and you're experienced with this idiom too boot, yet even you say
"other than [some major problems] it works," and I think missed that as
presented above it's not even safe for self-assignment -- that's how easy it
is for mistakes to slip through once we're off in the weeds of such fragile
constructs.
>In the end, Scott Meyers convinced me that I was
>wrong about this. I stopped recommending it because closed
>hierarchies generally end up becoming open.
I'm with Scott on this one, and I recommend against it for way more reasons
than just problems related to derived classes (which speaks only to problems
1 and 4 above).
Herb
---
Herb Sutter (mailto:hsu...@peerdirect.com)
CTO, PeerDirect Inc. (http://www.peerdirect.com)
Contributing Editor, C/C++ Users Journal (http://www.cuj.com)
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
That doesn't help. When you slam in a copy of MyClass with placement new
you get a vtable for MyClass, not for the derived type.
--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Contributing Editor, C/C++ Users Journal (http://www.cuj.com)
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Pete Becker <peteb...@acm.org> wrote in message
news:3A36B711...@acm.org...
> Risto Lankinen wrote:
> >
> > Just make MyClass MyClass::operator = (const MyClass &) virtual.
> > Then the derived class can easily avoid the "undefined behaviour of
> > the worst sort".
>
> That doesn't help. When you slam in a copy of MyClass with placement new
> you get a vtable for MyClass, not for the derived type.
Pardon me, sir, but yes, it does help:
virtual MyClass &MyClass::operator=( const MyClass &r )
{
if( this != &r )
{
this->MyClass::~MyClass();
new( this ) MyClass( r );
}
return *this;
}
Derived::Derived( const MyClass &r ) : MyClass(r)
{
// Initialize Derived-part as if in its default ctor
}
virtual MyClass &Derived::operator=( const MyClass &r )
{
// No slamming in a copy of MyClass tolerated here!!!
return *this = Derived( r );
}
virtual Derived &Derived::operator=( const Derived &r )
{
// Use the same idiom, or use memberwise assignment
}
MyClass *pb = new Derived;
MyClass m;
*pb = m; // '*pb' remains to be a Derived instance.
Then consider what if "MyClass::operator=( MyClass & )"
wasn't virtual.
There is a caveat that the operator=() MUST be overridden
by the derived class if the placement new trick is being used
in the base class. No biggie (at least to me), since there are
already things that you must remember: to make destructors
virtual, to make copy constructor if you grab resources, etc.
Also, any class can relieve subsequent derivations from the
burden (of having to override) by simply not using the idiom,
whereas the benefits (of being able to override existing base
assignment operators) will remain.
Cheers!
- Risto -
> James Kanze <James...@dresdner-bank.com> wrote in message
> news:3A36151F...@dresdner-bank.com...
> > > Original author wrote [or intended to write]:
> > > > MyClass MyClass::operator = (const MyClass &other) {
> > > > this->~MyClass() ;
> > > > new (this) MyClass(other);
> > > > return *this;
> > > > }
> > Most of all, if
> > someone derives from MyClass and doesn't use it, the results will
> > probably be undefined behavior of the worst sort.
> Just make MyClass MyClass::operator = (const MyClass &) virtual.
> Then the derived class can easily avoid the "undefined behaviour of
> the worst sort".
And how is that supposed to help. If the derived class doesn't
implement an operator=, say because it doesn't add any additional data
to copy, then after operator=, you only have an object of the base
type (although it is declared of the derived type, and the destructor
of the derived type will be called on it when it goes out of scope).
If the derived class does implement operator=, the "standard"
implementation is:
Derived&
Derived::operator=( Derived const& other )
{
Base::operator=( other ) ;
// copy rest of data...
return *this ;
}
Now, it's worse, since we have the vtable of a Base, but the data of a
Derived. Depending on whether the function call goes through the
vtable or not, you access a different function.
If the base class uses the OP's suggested solution, the only safe way
to implement operator= in a derived class is to do the same. And
since there is no way to ensure that the author of the derived class
does the same, you can't use the idiom safely.
--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
> Sometime later, James Kanze <James...@dresdner-bank.com> responded in
> part:
> >This idiom will cause problems if the copy constructor throws. It
> >isn't thread-safe (but then, operator= often isn't). Most of all,
> >if someone derives from MyClass and doesn't use it, the results
> >will probably be undefined behavior of the worst sort.
> >But other than that, it works.
> Well, I like the way you put that, because such heavily-caveated
> "damning with faint praise" speaks volumes. :-)
Sorry, litote is an almost standard French rhetorical idiom. But I do
like it expressed this way:
Other than the fact that it will almost certainly result in
undefined behavior in the long run, if not immediately, the idiom
works.
Actually, the idiom does have it uses. If you were fired abusively,
for example, and want to get back at the company, and can be sure that
it works in the current version of the software, then it's a nice
sleeper to leave in the code to cause later problems. And if there
were an obfuscated C++ contest, I could imagine using it there to
intentionally change the type from the declared type (being careful to
change it back to the original type before the final destructor is
called).
> Even so, I really really recommend against even such slight
> encouragement of this anti-idiom.
I hardly thought of what I wrote as encouragement, even slight. Just
a normal use of litote.
> It's fragile and fraught with peril. For example, you're an expert
> and you're experienced with this idiom too boot, yet even you say
> "other than [some major problems] it works," and I think missed that
> as presented above it's not even safe for self-assignment -- that's
> how easy it is for mistakes to slip through once we're off in the
> weeds of such fragile constructs.
I didn't bother to point out the self-assignment problem because it is
easily fixed, but the idiom remains broken even when the
self-assignment is fixed.
> >In the end, Scott Meyers convinced me that I was
> >wrong about this. I stopped recommending it because closed
> >hierarchies generally end up becoming open.
> I'm with Scott on this one, and I recommend against it for way more
> reasons than just problems related to derived classes (which speaks
> only to problems 1 and 4 above).
Most of the projects I've worked on haven't used exceptions, nor
threads. So these considerations are relative. On the other hand,
I've never seen code that didn't evolve. So that consideration is a
killer criteron. IMHO, of course, but you really don't need
additional reasons to not use it.
--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
> Pete Becker <peteb...@acm.org> wrote in message
> news:3A36B711...@acm.org...
> > Risto Lankinen wrote:
> > > Just make MyClass MyClass::operator = (const MyClass &) virtual.
> > > Then the derived class can easily avoid the "undefined behaviour of
> > > the worst sort".
> > That doesn't help. When you slam in a copy of MyClass with placement new
> > you get a vtable for MyClass, not for the derived type.
> Pardon me, sir, but yes, it does help:
> virtual MyClass &MyClass::operator=( const MyClass &r )
> {
> if( this != &r )
> {
> this->MyClass::~MyClass();
> new( this ) MyClass( r );
> }
> return *this;
> }
> Derived::Derived( const MyClass &r ) : MyClass(r)
> {
> // Initialize Derived-part as if in its default ctor
> }
> virtual MyClass &Derived::operator=( const MyClass &r )
> {
> // No slamming in a copy of MyClass tolerated here!!!
> return *this = Derived( r );
> }
No, no slamming in a copy of MyClass here. But this isn't a copy
assignment operator, so it isn't what will be called. (If it would be
called, it would also be called for the assignment here, thus infinite
recursion.) What will be called will normally be the compiler
generated default copy assignment, which will first call the operator=
of the base class, and then for each of the additional data members in
the derived class.
> virtual Derived &Derived::operator=( const Derived &r )
> {
> // Use the same idiom, or use memberwise assignment
> }
This has nothing to do with virtual-ness or not. What is important is
that the derived copy assignment operator does *not* call the base
class operator=. The default, compiler generated operator= does call
the base class operator=, and this is the standard idiom for user
supplied operator=.
> MyClass *pb = new Derived;
> MyClass m;
> *pb = m; // '*pb' remains to be a Derived instance.
> Then consider what if "MyClass::operator=( MyClass & )"
> wasn't virtual.
You will have problems regardless of the idiom used. Assignment
doesn't mix well with inheritance anyway.
> There is a caveat that the operator=() MUST be overridden
> by the derived class if the placement new trick is being used
> in the base class. No biggie (at least to me), since there are
> already things that you must remember: to make destructors
> virtual, to make copy constructor if you grab resources, etc.
The problem is that the other things are standard. They are always
true, regardless of the idiom, so all C++ programmers know and follow
them. Here, not only *must* every derived class furnish an operator=,
every derived class has to ensure that its operator= doesn't call the
base class operator= in any way.
--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
Sure, if you remember to do this for every derived type. Good luck...
--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Contributing Editor, C/C++ Users Journal (http://www.cuj.com)
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Okay, that avoids the problem. But look at the cost: every derived class
must provide several explicit assignment operators, one for each of its
bases. In your example, Derived provides two assignment operators. And a
class derived from Derived would have to provide three, one that takes a
const MyClass&, one that takes a const Derived&, and one that takes a
const MoreDerived&. And, of course, if MyClass gets refactored into two
bases and a derived class (a fairly common maintenance technique), then
Derived and MoreDerived must be rewritten to add the additional
assignment operators for the new base classes.
Fragile, handle with extreme care.
--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Contributing Editor, C/C++ Users Journal (http://www.cuj.com)
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
James Kanze <James...@dresdner-bank.com> wrote in message
news:3A376F0C...@dresdner-bank.com...
> Risto Lankinen wrote:
> > virtual MyClass &Derived::operator=( const MyClass &r )
> > {
> > // No slamming in a copy of MyClass tolerated here!!!
> > return *this = Derived( r );
> > }
>
> No, no slamming in a copy of MyClass here. But this isn't a copy
> assignment operator, so it isn't what will be called.
I'm unsure of what you mean by "copy assignment"...
This assignment will be called when a Derived instance is being
assigned to using a Base reference or pointer. It's purpose is to
upcast the operand of the assignment into a Derived() object
and call its own "natural" assignment.
> > virtual Derived &Derived::operator=( const Derived &r )
> > {
> > // Use the same idiom, or use memberwise assignment
> > }
>
> This has nothing to do with virtual-ness or not. What is important is
> that the derived copy assignment operator does *not* call the base
> class operator=.
Precisely!
> > Then consider what if "MyClass::operator=( MyClass & )"
> > wasn't virtual.
>
> You will have problems regardless of the idiom used. Assignment
> doesn't mix well with inheritance anyway.
I have been using this idiom since around 1992-1994 in (and now
I'm guessing) about 200 classes. When will my problems emerge?
> The problem is that the other things are standard. They are always
> true, regardless of the idiom, so all C++ programmers know and follow
> them. Here, not only *must* every derived class furnish an operator=,
> every derived class has to ensure that its operator= doesn't call the
> base class operator= in any way.
Please see my other posts of the same topic.
Cheers!
- Risto -
Pete Becker <peteb...@acm.org> wrote in message
news:3A37C211...@acm.org...
> Risto Lankinen wrote:
> >
> > [ snip ]
> >
> > virtual Derived &Derived::operator=( const Derived &r )
> > {
> > // Use the same idiom, or use memberwise assignment
> > }
>
> Okay, that avoids the problem
Thanks a lot for trying it out!
> But look at the cost: every derived class
> must provide several explicit assignment operators, one for each of its
> bases.
I am very sorry, but I must contradict you again. Only the
immediate base class assignment needs to be overridden,
no matter how many ancestors they may have.
> In your example, Derived provides two assignment operators. And a
> class derived from Derived would have to provide three, one that takes a
> const MyClass&, one that takes a const Derived&, and one that takes a
> const MoreDerived&.
Not true. MoreDerived will reuse Derived::operator=( Base ),
and only override Derived::operator=(Derived) and optionally
provide the same pattern to its own inheritants by providing a
virtual MoreDerived::operator=( MoreDerived ).
> And, of course, if MyClass gets refactored into two
> bases and a derived class (a fairly common maintenance technique), then
> Derived and MoreDerived must be rewritten to add the additional
> assignment operators for the new base classes.
Consider the insertion of a NewClass, say, between MyClass
and Derived. NewClass must override Base::operator=(Base),
that is all that is required to rewire my idiom.
NewClass _may_ also optionally provide a slamming assignment
operator in which case it must be made virtual, and be overridden
in Derived. The already existing override of MyClass assignment
in Derived may, but doesn't have to, be removed.
> Fragile, handle with extreme care.
:-)
Cheers!
- Risto -
James Kanze <James...@dresdner-bank.com> wrote in message
news:3A376D6F...@dresdner-bank.com...
> Risto Lankinen wrote:
> > Just make MyClass MyClass::operator = (const MyClass &) virtual.
> > Then the derived class can easily avoid the "undefined behaviour of
> > the worst sort".
>
> If the derived class does implement operator=, the "standard"
> implementation is:
>
> Derived&
> Derived::operator=( Derived const& other )
> {
> Base::operator=( other ) ;
> // copy rest of data...
> return *this ;
> }
>
> Now, it's worse, since we have the vtable of a Base, but the data of a
> Derived. Depending on whether the function call goes through the
> vtable or not, you access a different function.
I now understand your concern and I agree with your conclusion.
Solution is, don't call Base::operator=() explicitly if the base class
is known to slam its operand in. If you insist using this idiom you
should inherit, say, BaseProxy from Base and use a non-slamming
implementation for BaseProxy::operator( BaseProxy ). Any class
that inherits from BaseProxy can then use your idiom safely.
Before you label my solution with any qualifying attribute consider
that it is equivalent to choosing the direction in which two virtual
functions each of which can be implemented in terms of the other
may refer one another (e.g. if base implements function Base::A()
using virtual Base::B(), then Derived::B() may not be implemented
using any A() even if it would be semantically correct). However,
there is no remedy to this problem other than the author of Base
having to communicate to the author of a derived that B() may not
call A(). Our idioms just differ in this sense with respect to the
assignment operators of both classes.
> If the base class uses the OP's suggested solution, the only safe way
> to implement operator= in a derived class is to do the same. And
> since there is no way to ensure that the author of the derived class
> does the same, you can't use the idiom safely.
Well, the Base class cannot enforce overriding of its assignment
operator without rendering itself into an abstract class. If C++
had a hypothetical language feature that would allow instantiation
of a class which has pure virtual methods, then the assignment
should be declared with such attribute so that all derived classes
must address the issue before compiling, while Base would still
be usable as a stand-alone class.
In the absence of such feature, the best the Base class can do is
to assert that it is a genuine instance of itself at run-time, e.g. ...
Base &Base::operator=( const Base & )
{
assert( typeid(*this) == typeid(Base) );
...
}
By the way, James, why isn't this whole discussion moot to you?
I may be wrong (in which case I'm truly sorry), but I thought you
were advocating against inheriting from a concrete class, whereas
the whole idea of having non-virtual assignment operator in base
and derived separately communicates that there is indeed unique
data in each.
Again, that is based on a vague recollection of a past discussion
in one of these forums, and I am sorry if I misrepresented your
standpoint in above paragraph.
Cheers!
- Risto -
> > virtual Derived &Derived::operator=( const Derived &r )
> > {
> > // Use the same idiom, or use memberwise assignment
> > }
> Okay, that avoids the problem. But look at the cost: every derived
> class must provide several explicit assignment operators, one for
> each of its bases.
The real cost is even worse. The real cost is that sooner or later,
one of the derived classes will *NOT* provide the required assignment
operators. Inevitable and certainly, unless this is a throw-away
program. (Let's not forget, both Unix and Windows were originally
throw-away programs.)
--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
> James Kanze <James...@dresdner-bank.com> wrote in message
> news:3A376F0C...@dresdner-bank.com...
> > Risto Lankinen wrote:
> > > virtual MyClass &Derived::operator=( const MyClass &r )
> > > {
> > > // No slamming in a copy of MyClass tolerated here!!!
> > > return *this = Derived( r );
> > > }
> > No, no slamming in a copy of MyClass here. But this isn't a copy
> > assignment operator, so it isn't what will be called.
> I'm unsure of what you mean by "copy assignment"...
Copy assignment is the standard's name for the type of assignment
operator that it will generate automatically (or more correctly, the
assignment operator that you write which will inhibit the automatic
generation). Roughly speaking, it is an assignment operator where
both sides have the same type, modulo cv-qualifiers.
> This assignment will be called when a Derived instance is being
> assigned to using a Base reference or pointer. It's purpose is to
> upcast the operand of the assignment into a Derived() object and
> call its own "natural" assignment.
Right. It's not a copy assignment -- the compiler will still generate
a Derived::operator=( Derived const& ). With the default semantics of
calling MyClass::operator=.
> > > virtual Derived &Derived::operator=( const Derived &r )
> > > {
> > > // Use the same idiom, or use memberwise assignment
> > > }
> > This has nothing to do with virtual-ness or not. What is
> > important is that the derived copy assignment operator does *not*
> > call the base class operator=.
> Precisely!
> > > Then consider what if "MyClass::operator=( MyClass & )" wasn't
> > > virtual.
> > You will have problems regardless of the idiom used. Assignment
> > doesn't mix well with inheritance anyway.
> I have been using this idiom since around 1992-1994 in (and now I'm
> guessing) about 200 classes. When will my problems emerge?
When you start actually trying to assign through the base pointers.
--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
Sent via Deja.com
http://www.deja.com/
> James Kanze <James...@dresdner-bank.com> wrote in message
> news:3A376D6F...@dresdner-bank.com...
> > Risto Lankinen wrote:
> > > Just make MyClass MyClass::operator = (const MyClass &) virtual.
> > > Then the derived class can easily avoid the "undefined behaviour
> > > of the worst sort".
> > If the derived class does implement operator=, the "standard"
> > implementation is:
> > Derived&
> > Derived::operator=( Derived const& other )
> > {
> > Base::operator=( other ) ;
> > // copy rest of data...
> > return *this ;
> > }
> > Now, it's worse, since we have the vtable of a Base, but the data
> > of a Derived. Depending on whether the function call goes through
> > the vtable or not, you access a different function.
> I now understand your concern and I agree with your conclusion.
> Solution is, don't call Base::operator=() explicitly if the base
> class is known to slam its operand in. If you insist using this
> idiom you should inherit, say, BaseProxy from Base and use a
> non-slamming implementation for BaseProxy::operator( BaseProxy ).
> Any class that inherits from BaseProxy can then use your idiom
> safely.
Right. Don't forget, however, that compiler generated operator=
*will* call the base operator=. And that the standard idiom for most
programmers is to call operator=.
I used to use the idiom myself. It works for closed hierarchies,
where you can actively control the operator= of all the classes
involved. The problem is that in real life, closed hierarchies never
are. Sooner or later, some one else is going to add a class to your
hierarchy. And bang.
> Before you label my solution with any qualifying attribute consider
> that it is equivalent to choosing the direction in which two virtual
> functions each of which can be implemented in terms of the other may
> refer one another (e.g. if base implements function Base::A() using
> virtual Base::B(), then Derived::B() may not be implemented using
> any A() even if it would be semantically correct). However, there
> is no remedy to this problem other than the author of Base having to
> communicate to the author of a derived that B() may not call A().
> Our idioms just differ in this sense with respect to the assignment
> operators of both classes.
No. The normal solution in this case is that the author of Base
decides which function is implemented in terms of the other, and makes
it non virtual. In such cases, it is clear to the author of the
derived class that he should not call it.
> > If the base class uses the OP's suggested solution, the only safe
> > way to implement operator= in a derived class is to do the
> > same. And since there is no way to ensure that the author of the
> > derived class does the same, you can't use the idiom safely.
> Well, the Base class cannot enforce overriding of its assignment
> operator without rendering itself into an abstract class. If C++
> had a hypothetical language feature that would allow instantiation
> of a class which has pure virtual methods, then the assignment
> should be declared with such attribute so that all derived classes
> must address the issue before compiling, while Base would still be
> usable as a stand-alone class.
> In the absence of such feature, the best the Base class can do is to
> assert that it is a genuine instance of itself at run-time, e.g. ...
> Base &Base::operator=( const Base & )
> {
> assert( typeid(*this) == typeid(Base) );
> ...
> }
> By the way, James, why isn't this whole discussion moot to you? I
> may be wrong (in which case I'm truly sorry), but I thought you were
> advocating against inheriting from a concrete class, whereas the
> whole idea of having non-virtual assignment operator in base and
> derived separately communicates that there is indeed unique data in
> each.
I don't argue (strongly) against deriving from a concrete class. I do
know that it is not considered good practice in many circles, for a
number of reasons. And in practice, I find that clean designs don't
inherit from concrete classes. At least not very often.
> Again, that is based on a vague recollection of a past discussion in
> one of these forums, and I am sorry if I misrepresented your
> standpoint in above paragraph.
I may have said something to that effect, but if so, I was simply
repeating what a number of well respected experts say. It's not
something I feel strongly about, but I also haven't found any really
strong reasons to say that the experts are wrong. I've very
ambivilent about it.
--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
Sent via Deja.com
http://www.deja.com/
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Sorry, I didn't read your code carefully enough.
>
> > In your example, Derived provides two assignment operators. And a
> > class derived from Derived would have to provide three, one that takes a
> > const MyClass&, one that takes a const Derived&, and one that takes a
> > const MoreDerived&.
>
> Not true. MoreDerived will reuse Derived::operator=( Base ),
> and only override Derived::operator=(Derived) and optionally
> provide the same pattern to its own inheritants by providing a
> virtual MoreDerived::operator=( MoreDerived ).
>
Yes, because you've now abandoned this approach. So why not abandon it
in Base as well? No risk of slamming the wrong type at all, just the
overhead of creating a temporary object...
--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Contributing Editor, C/C++ Users Journal (http://www.cuj.com)
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
James Kanze <James...@dresdner-bank.com> wrote in message
news:3A38B20C...@dresdner-bank.com...
> Pete Becker wrote:
> > Okay, that avoids the problem. But look at the cost: every derived
> > class must provide several explicit assignment operators, one for
> > each of its bases.
>
> The real cost is even worse. The real cost is that sooner or later,
> one of the derived classes will *NOT* provide the required assignment
> operators.
I replied to Pete's claim more than 24 hrs ago, but I haven't seen
it in my server yet so please bear with me if my reply is repeated.
Anyway, any given class needs only to override the assignment
operator of its _immediate_ base class, and even then only if it
uses the placement new.
The claim that _all_ base class assignment operators need to be
overridden is blatantly false.
> Inevitable and certainly [one of the derived classes will *NOT*
> provide the required assignment operators], unless this is a
> throw-away program.
I fail to see why it is inevitable or certain. Careful and skilled
maintenance programmers can easily avoid any problems of
this kind - heck, even I could! And if properly implemented,
the idiom gives plenty of advance warning during unit testing
(you do test before release, don't you?)
> (Let's not forget, both Unix and Windows were originally
> throw-away programs.)
Some might argue that they still are now that there's Linux... :-)
Cheers!
- Risto -
If that policy becomes commonplace, I'll have to watch whose names
I mention ;)
> If the attributions are correct, then it was Peter Niessen who wrote:
Actually, I think I should admit to being the author of this; I think
that it was correctly attributed, but that's not obvious with the number
of people who have contributed to this thread.
To fix some context, I posted in reply to a message over on comp.std.c++
where (re-introducing text from the original message)
> > Peter Niessen wrote:
> > > do I need an assignment operator for a class if I have a copy
> > > constructor? Do I need a copy constructor if I have an
> > > assignment operator? My gcc version 2.95.2 19991024 on HP-UX
> > > goes into infinite loops if I declare both like
> > > MyClass (const MyClass &OldMyClass);
> > > MyClass operator = (const MyClass &OldMyClass);
> > > and it seems that is tries to do a copy in the parameter list
> > > although it's declared with &. Thus the program ends up in an
> > > infinite loop when doing
> > > MyClass mc1;
> > > MyClass mc2 (mc1);
This seemed, to me, to indicate some confusion as to the
relationship between copy construction and copy assignment.
As you (Herb) mention, the canonical exception-safe way to
implement copy assignment is to use copy construction and
a no-throw swap. The reverse, implementing construction
in terms of assignment makes no sense [to me], even if it
happens to work for many (but not all) cases. I suspected
that the OP (Peter Niessen) might have come across code
which attempted to implement the copy ctor in terms of the
assignment operator, so blowing the stack when the assignment
needed to use a copy ctor to return MyClass by value.
Now back to the message to which I am replying.
> >> > If the assignment operator looks like
> >
> >> > MyClass MyClass::operator = (const MyClass &other) {
> >> > ~MyClass();
> >> > new (this) MyClass(other);
> >> > return *this;
> >> > }
> >
> >> > then it shouldn't. That approach isn't exception safe, and it
> >> > messes with the notion of object lifetimes in a way which is
> >> > likely to make Herb Sutter feel unwell if he reads this post.
>
> Since you single me out, I suppose it's likely because of either my November
> 2000 CUJ column, or Item 41 in Exceptional C++ which is devoted entirely to
> debunking this anti-idiom.
When I think of a crusade against ugly code which fails to be
exception safe, "Exceptional C++" and GotW do spring to mind.
> If you haven't read Item 41 yet, please do. As I point out in that Item,
> this approach has lots of problems, especially as written above: 1. As
> commonly implemented, it can slice objects. 2. It's not exception-safe (as
> you noted). 3. It plays havoc with normal object lifetimes (as you noted).
> 4. It makes life hellish for derived classes. 5. As written above, it's not
> safe for self-assignment; the self-assignment test is mandatory (and, alas,
> insufficient because of the other problems).
The failure for self-assignment was an accident. In real code I
don't write assignment operators which need a test, so I didn't
think to include it when posting this code.
> In the end, the main point Item 41 makes is that it's indeed okay to
> implement copy assignment in terms of copy construction -- but this
> anti-idiom is NOT the way to do it. Instead, used the common true idiom:
>
> T& T::operator=( const T& other )
> {
> T temp( other );
> Swap( temp ); // Swap is a nothrow function
> return *this;
> }
>
> See Item 41 for details and discussion. Interestingly, a few people have
> told me that they felt I spent too much space protesting this anti-idiom
> when it's clearly wrong and not worth the thorough spanking I give it; my
> response to them has been that it's needed because the anti-idiom keeps
> cropping up in the newsgroups. :-) Don't feel bad, though, you're not alone
> and even some experts encouraged it in the early days.
For the record: I was the original poster (in this thread)
of this horrible code, and posted it only to illustrate one
way _not_ to write copy assignment.
-- James Dennett <jden...@acm.org>
Thanks for the correction. That's what I wrote originally from
memory, but a strange erroneous style demon appeared and deleted
the "this->", claiming that it was redundant.
-- James Dennett <jden...@acm.org>