views:

267

answers:

5

I have a virtual class that has been derived from a non virtual class. But when I c-style cast the derived class to base class, the class is corrupted. I am looking at the member variables using the debugger and the member variables are all corrupted when I do that cast. I see there is a 4 byte discrepency when I do that cast(may be the virtual pointer) using debugger. For Ex:

class A//non-virtual class
{
 ~A();
int fd;
};
class B:public A
{
virtual ~B();
};

Now say the address of obj of type B is: 0x9354ed0. Now when I cast it (A*)(0x9354ed0) debugger moves the bytes by 4 bytes. So starting address of the casted obj is 0x935ed4

Is it wrong to cast a derived virtual class to base non-virtual class? What is the reason for 4 byte discrepancy? And what is the right way to cast it? Thanks for any input or explanation.

A: 

If that is the shell of the code you are using then you aren't deriving the class B from class A. If you wanted to do that you would need to do this:

class A
{
public:
  virtual ~A();
  // other stuff
};

class B: public A
{
public:
  virtual ~B();
  // other stuff
};

I really don't think you can cast the way that you were hoping to in your question. If you are using virtual functions and derived classes then they need to be declared virtual upon first usage. Plus, I'm not 100% sure but if you have any virtual destructors you need to have all virtual destructors in your heirarchy.

Hope that helps, nmr

nmr
Although class A won't be virtual, a class C derived from B could be cast back to B* and be virtual there. So there is a use case, just kind of rare and unadvised.
Mark Ransom
+8  A: 

Here is what you described.

class A
{
public:
  ~A();
  // other stuff
};

class B: public A
{
public:
  virtual ~B();
  // other stuff
};

It is perfectly legal to derive a class from a non-virtual base class, and to make the derived class virtual. The 4 bytes offset (can be 8) corresponds to a hidden member variable of type pointer that points to the virtual functions table of the class.

Didier Trosset
+1. Yes, the 4 bytes sounds right to me too. The base class A is at offset 4 within the derived class B. The implementation does this because B has a vtable pointer at the start, whereas A doesn't. Supposing that A has a protected member `x`, then `((A*)myObj)->x` will compile to code that applies a different offset to the A*, from what `((B*)myObject)->x` applies to the B*.
Steve Jessop
@Steve: While the remark about VMT pointer is correct, I don't get your point about member `x`. Where is `x` declared? If `x` is a member of `A` (as it seems from your examples), the value of `x` will be accessed correctly in both cases. It won't be "corrupted" in any way. Where does the "corruption" come from then?
AndreyT
Yes, I'm supposing that A has a protected member `x`. I didn't say it would be corrupted, I said that it would be accessed using different offsets from the different object pointers. Based on what he says he sees in the debugger, I reckon the questioner just thinks his A* pointer is corrupted, because it doesn't point to the same thing as his B* pointer. If the object really is trashed, then it's for reasons not available from the question...
Steve Jessop
A: 
  1. Its legal to point derived class object using base class pointer thats called run time polymorphism i.e. based on dynamic type of object funtions are dispatched.

  2. When you define a class with virtual function,a table comprising of virtual function address is formed called virtual function table. When you declare an object of a class with virtual function , a virtual pointer pointing to corresponding virtual function table is insert into it.

Ashish
+1  A: 

When you cast pointers across the hierarchy, actual numerical pointer values might change. There's nothing wrong with the fact that it changes. There's noting wrong with such a cast. How the numerical pointer values change depend on the physical layout of the class. It is an implementation detail.

In your example, the 4 byte change in pointer value might easily be caused by the presence of virtual table pointer in the derived class.

The change in pointer value will not corrupt any member variables. The example in your original post does not show any "corrupted member variables" and in general your claim about some member variables getting "corrupted" doesn't make much sense. Please, when you make such claims, illustrate them with an example, so that people can understand what on Earth your are talking about.

Update:

The fact that base class subobjects might have non-zero offsets inside the derived classes immediately mean that in order to perform a proper cast from derived pointer type to base pointer type the compiler must know the source type and the destination type and the relationship between them. For example, if you do this

B* pb = /* whatever */;
A* pa = pb;

the compiler will know how to properly offset pointer pa from pointer pb. But of you do this

A* pa = (void *) pb;

the compiler will forget about the relationship between A and B, fail to perform the proper offset and produce the invalid value of pa. The effect would be the same as in

A* pa = reinterpret_cast<A*>(pb);

If you do it this way, the result will be meaningless. So, just don't do it.

Of course, if you manually inspect the numerical value of pb and find out that it is, say, 0x12345678, and then do

A* pa = (A*) 0x12345678;

you will also get completely meaningless results, because the computer has no way to know that it has to perform the proper offset of the pointer.

This will produce an illusion of members of A getting "corrupted", while in fact the only thing that is corrupted is your pointer. And you are the one who corrupted it.

AndreyT
A: 

The missing four bytes are, indeed, the vptr of the object; since class A has no vtable, a pointer to A has no vptr.

While it is actually legal deriving a class with virtual methods from another class with none, it is not a good idea. Inheriting from a class with no virtual methods is usually unsound, since that class is not intended to be used polymorphically, which is the main reason behind inheritance. All kinds of bad things can happen if you do it. If you try to use derived class objects through base class pointers, you may well run into the dreaded undefined behaviour (most likely a crash). For instance, trying to delete a base class pointer pointing to a derived class object.

Gorpik
Key word here being "usually", where we assume that "usually" programmers are dumb, and will delete through base pointers even when the documentation tells them not to. `boost::noncopyable` is a base class with no virtual members, and quite right too. It's unsound to delete any object through a `noncopyable*`, but it's not unsound to use it as a base class, and `noncopyable` is not a bad idea (at least, not for this reason. It does have a silly name).
Steve Jessop
Well, usually you don't want to violate Liskov Substitution Principle.
Gorpik
Sort of. I think lifecycle in C++ is often outside the scope of the "universally available" interface. There are a lot of reasons in C++ why you say, "do not delete this object". Saying, "do not delete this object unless you know how it was created" is not that unreasonable, IMO. So for instance noncopyable vacuously satisfies Liskov, because there's *nothing* you can validly do with a noncopyable object, and therefore nothing subclasses can fail to do. That's why I think "not a good idea" overstates the case. I'd say, "only a good idea if there's a particular reason for it you can document".
Steve Jessop
I also dispute that polymorphism is the "main" reason for inheritance in C++. It's one of the big ones, of course, but C++ also commonly uses non-polymorphic base classes as mixins (like the iterator base classes and noncopyable) and for CRTP. What you say about polymorphic bases is absolutely true, but just because something is a base of a polymorphic class doesn't mean there needs to be polymorphism involving the base. A protected non-virtual destructor is a good idea in many such cases. Liskov only applies where there is polymorphism, and C++ base classes are "allowed" to opt out of that.
Steve Jessop