Reputation: 27385
could you possibly explain what is difference between ill-formed cast
and failed cast
.
For instance:
class A { virtual void f(); };
class B { virtual void g(); };
class D : public virtual A, private B { };
void g() {
D d;
B* bp = (B*)&d; // cast needed to break protection
A* ap = &d; // public derivation, no cast needed
D& dr = dynamic_cast<D&>(*bp); // fails
ap = dynamic_cast<A*>(bp); // fails
bp = dynamic_cast<B*>(ap); // fails
ap = dynamic_cast<A*>(&d); // succeeds
bp = dynamic_cast<B*>(&d); // ill-formed (not a run-time check)
}
Upvotes: 0
Views: 165
Reputation: 43662
Let's start from the beginning and take a look at every case:
class A { virtual void f() {} };
class B { virtual void g() {} };
class D : public virtual A, private B { };
void g() {
D d;
B* bp = (B*)&d;
[continue...]
C++ casts (exception for reinterpret_cast
which just casts raw pointer value with no adjustment or arithmetic at all) aren't allowed to "ignore" inheritance access levels (https://stackoverflow.com/a/3674955/1938163), C-style casts can.
With the above code bp
is a pointer to a valid object, but the access level is bypassed completely. Could this be a problem?
The answer is yes, take the following as an example:
class A { virtual void f() {} };
class B { virtual void g() {}
public:
~B() {cout << "B's destructor";} // You can destroy B objects but NOT D objects from B* pointers
};
class D : public virtual A, private B {
~D() {cout << "D's destructor";}
};
void g() {
D *d = new D();
B* bp = (B*)d; // Bypass access permissions
delete bp; // This shouldn't happen! D's destructor will NOT be called! Undefined Behavior!
Compiling with -Wold-style-cast -Werror
avoids this problem (and several others as well: https://stackoverflow.com/a/12765440/1938163)
Continuing with your example we have
A* ap = &d;
and this is a perfectly legit upcast. What is not legit is the following cast:
D& dr = dynamic_cast<D&>(*bp);
Citing from the standard and substituting some words for readability reasons:
(N3690 - §5.2.7 - 8)
If D is the class type to which D& points or refers, the run-time check logically executes as follows: — If, in the most derived object pointed (referred) to by bp, bp points (refers) to a public base class subobject of a D object, and if only one object of type D is derived from the subobject pointed (referred) to by bp the result points (refers) to that D object.
thus the access permissions are wrong and the cast fails. It is NOT ill-formed, just fails (read later for the difference).
Had you asked for a pointer you would have got a NULL
one, but since a reference needs to be bound to an object the above throws an exception (it's the only sensible thing to do here).
The cast that follows also isn't ill-formed but just plain wrong. Usually you can't cast from a base pointer to another base (https://stackoverflow.com/a/7426562/1938163) but since the base classes involved here are polymorphic, the following should be allowed and valid:
ap = dynamic_cast<A*>(bp);
... if bp points (refers) to a public base class subobject of the most derived object, and the type of the most derived object has a base class, of type A, that is unambiguous and public, the result points (refers) to the A subobject of the most derived object.
but again: the permissions are getting things wrong and the cast is failing (NOT ill-formed, again just failing).
The cast that follows is already an invalid cast since ap
is NULL
bp = dynamic_cast<B*>(ap);
but if ap
weren't NULL
, the cast would have failed anyway for the same passage cited above:
... if ap points (refers) to a public base class subobject of the most derived object, and the type of the most derived object has a base class, of type B, that is unambiguous and public, the result points (refers) to the B subobject of the most derived object.
The only cast that succeeds is the
ap = dynamic_cast<A*>(&d);
where a D
object pointer is casted to a public base class.
The last cast is finally ill-formed
bp = dynamic_cast<B*>(&d);
since according to the standard
(N3690 - §5.2.7 - 5)
(dynamic_cast)
If B* is “pointer to cv1 B” and &d has type “pointer to cv2 D” such that B is a base class of D, the result is a pointer to the unique B subobject of the D object pointed to by &d.
...
In both the pointer and reference cases, the program is ill-formed if cv2 has greater cv-qualification than cv1 or if B is an inaccessible or ambiguous base class of D.
Grand-total: 3 failed casts (one with exception throwing), one succeeded and one ill-formed.
Finally: failed casts are casts which could not be accomplished but they can be handled according to the standard rules:
If the value of v is a null pointer value in the pointer case, the result is the null pointer value of type T. If C is the class type to which T points or refers, the run-time check logically executes as follows:
...(same rules as above)
— Otherwise, the run-time check fails.
The value of a failed cast to pointer type is the null pointer value of the required result type. A failed cast to reference type throws an exception
Unless instructed otherwise (no diagnostics required) a compiler implementation is usually supposed to emit an error or a warning for an ill-formed program, in the casts you're interested in:
B* bp = (B*)&d;
A* ap = &d;
D& dr = dynamic_cast<D&>(*bp); // This is a runtime error
ap = dynamic_cast<A*>(bp); // this is a runtime error
bp = dynamic_cast<B*>(ap); // this is a runtime error
ap = dynamic_cast<A*>(&d); // succeeds
bp = dynamic_cast<B*>(&d); // This is ill-formed and the compiler should warn about it
If I got something wrong (very likely) please write it down in the comments below and I'll fix my post immediately. Thanks!
Upvotes: 1
Reputation: 137315
Despite the name, when you use dynamic_cast
to do an upcast (derived->base), the cast is done at compile time and behaves the same way as a static_cast
or an implicit conversion - if the base is ambiguous or inaccessible the program is ill-formed, meaning that the compiler must produce a diagnostic. §5.2.7 [expr.dynamic.cast]/p5:
[For the expression
dynamic_cast<T>(v)
:]If
T
is "pointer tocv1 B
" andv
has type "pointer tocv2 D
" such thatB
is a base class ofD
, the result is a pointer to the uniqueB
subobject of theD
object pointed to by v. Similarly, ifT
is "reference tocv1 B
" andv
has typecv2 D
such thatB
is a base class ofD
, the result is the uniqueB
subobject of theD
object referred to byv
. 67 The result is an lvalue ifT
is an lvalue reference, or an xvalue ifT
is an rvalue reference. In both the pointer and reference cases, the program is ill-formed ifcv2
has greater cv-qualification thancv1
or ifB
is an inaccessible or ambiguous base class ofD
.67 The most derived object (1.8) pointed or referred to by v can contain other B objects as base classes, but these are ignored.
In other cases (casting down or sideways), the checks are performed at run-time. If it fails, then the cast result is a null pointer for pointer casts, and a std::bad_cast
exception for reference casts.
Upvotes: 4