Re: Virtual bases and default move and swap functions [n2583, n2584]
On May 17, 12:32 am, "Martin T." <0xCDCDC...@gmx.at> wrote:
Richard Smith wrote:
In a virtual inheritance hierarchy, the implicitly-declared assignment
operator is allowed to assign the base class multiple times
[[class.copy] 12.8/13]:
struct V {
V& operator=(V const&) { std::cout << "Foo!\n"; }
};
struct A : virtual V {};
struct B : virtual B {};
struct D : A,B {};
int main() { D d1, d2; d1 = d2; }
I assume you meant?:
struct V {
V& operator=(V const&) { std::cout << "Foo!\n"; return *this; }};
Yes. That was just a silly typo.
struct A : virtual V { };
struct B : virtual A { };
struct D : virtual A, virtual B {};
You're right there was a typo here, but this isn't it. I was just
trying to write a standard virtual inheritance diamond:
struct V { ... };
struct A : virtual V {};
struct B : virtual V {}; // <-- note V instead of B
struct D : A, B {};
For a virtual inheritance diamond, you only need to use the keyword
virtual when referring to the common base class (V in this example).
See 10.2/5 for almost exactly this example.
In this example, it is unspecified how many times "Foo!" is printed
out, and many compilers will do the assignment twice. (In this
particular example, compliers could reasonably get it 'correct' and
only print "Foo!" once; add user-supplied assignment operators to A
and B and this is no longer the case.)
Apologies for digressing from your original question, but what's the
rational of the standard allowing this?!?
Because in many cases, it would be virtually impossible (no pun
intended) for the compiler to do anything else. Imagine V, A and B
all have user-defined assignment operators, with implementations
buried away in a source file somewhere. In practice the
implementations of A and B are likely to look something like:
A& operator=( A const& o ) {
V::operator=(o);
// assign members
return *this;
}
But, of course, it needn't be exactly like this: it might not
explicitly call V::operator= (it might effectively have the definition
of V::operator= inlined by hand). Or the author might know that
V::operator= is a no-op and omit the call.
Now, give this; and specifically given that the implementations of
A::operator=, B::operator= and V::operator= may not be visible to the
compiler, what should the compiler-generated D::operator= look like?
The only reasonable definition is:
D& operator=( D const& o ) {
A::operator=(o);
B::operator=(o);
// and then assign each member ...
return *this;
}
And this will generally double-assign the base class, V, because
A::operator= will assign it, and then B::operator= will do it again.
However, for most classes, the double assignment is safe -- it is
merely a slight inefficiency.
By contrast, the copy constructor can be correctly handled. (And the
standard requires it to be.) Each class' constructor *must* construct
each base class precisely once. This means that the compiler simply
generates two versions of the constructor: one that initialises
virtual bases, and one that doesn't.
So:
V::V( V const& o );
.... no virtual bases so both copy constructors do the same thing.
A::A( A const& o );
B::B( B const& o );
.... both generate two functions each, one that copies V and one that
does not
D::D( D const& o );
.... this one also generates two functions, one of them starts by
copying V; the other one omits this step. Then both functions call
the versions of constructors of A and B that do not copy V. And when
I actually invoke this:
D x; D y(x);
this calls the version of the D copy constructor that copies V, then A
then B. The result is that all the classes are copied precisely once.
But you can't do this with copy assignment operator because the
compiler can't tell which bit of A::operator= is copying V. And this
is because there is no requirement for A::operator= to contain exactly
one call to V::operator=, whereas there is a requirement for A::A(A
const&) to contain exactly one call to V::V(V const&).
What's more, aren't the objects of the following classes exactly the
same (with the apparent difference in copy behavior)?? :
struct D_1 : B {};
struct D_2 : virtual B {};
struct D_3 : virtual A, B {};
struct D_4 : virtual A, virtual B {};
Yes, they're all identical. (At least until you consider the question
of subclassing them: if you do, you'll find that D_1 and D_3 are the
same, as are D_2 and D_4, are the two pairs differ by whether B is a
virtual base. In all cases A is already a virtual base.)
But this is largely irrelevant to my question. In today's language,
the double assignment in the copy assignment operator is generally
harmless, and only causes a problem when the virtual base class'
members have peculiar copy assignment semantics. But in these two
proposals, the move assignment operator and member swap functions will
both cause problems when the virtual base class' members have normal
move assignment and swap semantics. This is the crucial difference.
--
Richard Smith
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]