11

Is it possible to downcast an object to a subclass does not define any extra variable or virtual method?

If I have these classes,

class A { public: A (); };
class B : public A { public: void method1 () {} B (); };

is this (1) possible and (2) safe by standard?

A* a = new A ();
B* b = (B*)a;
b->method1();
Fiktik
  • 1,941
  • 16
  • 25
eonil
  • 83,476
  • 81
  • 317
  • 516
  • 7
    No, an `A` isn't a `B`. – Bo Persson Feb 19 '13 at 08:00
  • Your question doesn't match what you're asking. Your question asks about casting objects, whereas your code is casting pointers. – user541686 Feb 19 '13 at 08:05
  • 1
    Related: http://stackoverflow.com/questions/11404209/c-memory-layout-of-inheritance – jogojapan Feb 19 '13 at 08:05
  • @Mehrdad I realized the problem on my question. I added a line which can make my question more clear. – eonil Feb 19 '13 at 08:06
  • 1
    @Andrey This question is derived from my another question: http://stackoverflow.com/questions/14952141/objective-cs-category-like-construct-or-technique-in-c. – eonil Feb 19 '13 at 08:57

4 Answers4

7

The pointer conversion acts as a static_cast. 5.2.9/2 says,

If the object of type [A] is actually a subobject of an object of type [B], the result refers to the enclosing object of type [B]. Otherwise, the result of the cast is undefined.

Your object is not a subobject of a B object, so the result is undefined.

Even if you were to reinterpret_cast, accessing the value of the object through the resulting pointer has undefined behavior because it violates strict aliasing. In the C++11 standard, 3.10/10:

If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

"A derived class of the dynamic type of the object that adds no data members or virtual member functions" is not in the list that follows.

Depending on what method1 actually does, it might be possible to avoid accessing the stored value of the object when calling it. But I'm not certain it's ever possible. Unless stated otherwise elsewhere in the standard I would assume for safety that calling a non-static member function inherently "accesses the stored value of the object" even if the function doesn't actually use any data members.

This is one of those awkward cases that will probably work in practice either all the time or almost all the time. But it's not guaranteed, so even if it appears to work and the emitted code looks OK, you will live in fear that some day a new optimization will break it.

Once defined, C++ classes are closed to new members, including new member functions. So your object created with new A() has all the member functions it will ever have. Just write a non-member function -- unless A has protected members it will have exactly the same access to A that your member function has. And if A does have protected members then there's an approved way of deriving from it, which you should use to create proper instances of B.

If member function syntax means that much to you, then depending on the class A you might be able to write:

B b = *a;    // "copy" the object (give B a suitable ctor)
b.method1(); // act on it
*a = b;      // copy it back (A needs copy assignment operator)

Obviously there are issues here that could stop it working: to begin with whether the object is copyable, also thread-safety, and whether method1 stores a pointer/reference to b somewhere that will start to dangle as soon as b is destroyed. In C++11 the copies could perhaps be moves for efficiency, but even so I hope you will agree that the hoops you have to jump through to use member function syntax are not worth it.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 2
    Regarding your last code, a more convenient way could be to make `B` a wrapper around `A`: instead of copying the object into `b`, `b` would keep a reference/pointer to `a`, and act on it. This is a fairly common pattern. – Luc Touraille Feb 19 '13 at 13:56
  • actually it's not possible for there to be a problem with the pointer conversion as long as the derived class has no data members. – Erik Aronesty Feb 18 '15 at 19:24
  • @ErikAronesty: you should definitely post an answer citing where the standard guarantees that, if it does. If my answer is wrong then I can't delete it since it's accepted, but I can refer to yours in my correction. – Steve Jessop Feb 18 '15 at 19:30
4

B* b = static_cast< B* >a or what you tried is an unsafe downcasting,it assigns the address of a base-class object (A) to a derived class (B) pointer. So if you access anything throught that pointer, it will cause undefined behavior.

Let's assume one possible object layout of an instance of A:

a ->|vptr|

When you perform a forced cast:

b ->|vptr|

it is definitely unsafe since a doesn't point to an instance of B or subclass of B. When you call a virtual method or modify a field(not in this case), it will cause undefined behavior in general or error in this layout.

However, your method1 is non-virtual, so there is no need to look up virtual table. Since your implementation of the method1 doesn't and even can't do anything on "this", So when you run the code in this hypothetical object layout, it will probabaly report no error(See James' comment).

StarPinkER
  • 14,081
  • 7
  • 55
  • 81
  • Hmm. From your example layouts, it seems as if they were actually identical. Also note that in the code given by the OP there are no virtual functions involved. – jogojapan Feb 19 '13 at 08:27
  • Methods are not stored in objects, there is no offset involved for invoking a member function (unless it is virtual, in which case the function is retrieved from the virtual method table, which is not stored in the object either). – Luc Touraille Feb 19 '13 at 08:27
  • Thanks, just noticed that method1 is non-virtual. – StarPinkER Feb 19 '13 at 08:54
  • 2
    It will probably report no error. According to the C++ standard, it's undefined behavior, and a compiler could generate code which would check the case, and cause it to fail in some way. – James Kanze Feb 19 '13 at 10:06
  • Yeah, good comment, I tested on MSVC++, it report no error. But like you said, compiler can make it fail. It is undefined behavior. @JamesKanze – StarPinkER Feb 19 '13 at 10:09
4
  1. Yes, it is possible.
  2. No, it is not safe according to the standard; it is discouraged.

This is more like C void* casting, which is not the best thing to do in C++. But I did this many times and it works fine.

jogojapan
  • 68,383
  • 11
  • 101
  • 131
Yousf
  • 3,957
  • 3
  • 27
  • 37
  • there are lots of great uses for downcasting. like defining a "workspace" and then defining derived "algorithms" that have no data members that operate on that workspace. The algorithms are better implemented as derived... but the pointers from one algorithm can always be safely cast to the base. imagine a factory that produces these... the base need not know what the vtable is. all it does is iterate and execute. – Erik Aronesty Feb 18 '15 at 19:26
  • @ErikAronesty, your argument is significant. It's different with anyone else, and I see what you said happens in real world product. Could you please elaborate a little more? I.e. it's NOT unsafe. – solotim Sep 07 '15 at 08:45
1

You should read this extremely well-written post:

Regular cast vs. static_cast vs. dynamic_cast

If you use "static_cast", be warned that you are "force" converting and this is inherently unsafe.

Community
  • 1
  • 1
Rahul Banerjee
  • 2,343
  • 15
  • 16