what actually happens during a c- style typecasting regarding address space of casted objects?
According to book, C++ FAQ by Cline,the following code depicts improper inheritance. notice that i can add a banana which is a kind-of fruit into a bagofApples which should actually contain only apples.. but due to the inheritance relationship banana is added to the bagofApples.
but the question is what actually happens at this line:
{ return (Apple&) BagOfFruit::remove(); }//what does happen here?
what does the c-style type casting do here? notice that the sizeof(banana) is 4004,while that of an apple is only 4. so though the remove function of the banana object is accessed by the apple object because the apple's function's address offset would be matching with that of banana's,but when the banana is typecasted to an apple object what happens to the data member ar[1000] of the banana? where does it lie in memory for the apple& reference here,what happens to the address space of the casted banana object(actual object)?
Apple& a2 = bagOfApple.remove();
The full code follows.
#include "stdafx.h"
#include <iostream>
using namespace std;
class Fruit
{
public:
virtual void printClassName() const throw() = 0;
virtual ~Fruit() throw();
};
Fruit::~Fruit() throw()
{ }
class Apple : public Fruit
{
public:
virtual void printClassName() const throw();
};
void Apple::printClassName() const throw()
{ cout << "Apple\n"; }
class Banana : public Fruit
{
public:
virtual void printClassName() const throw();
protected:
int ar[1000];
};
void Banana::printClassName() const throw()
{ cout << "Banana\n"; }
//The following BagOfFruit class allows insertion and removal of objects of any
//kind-of Fruit.
class Full { };
class Empty { };
class BagOfFruit
{
public:
BagOfFruit() throw();
unsigned size() const throw();
void in开发者_StackOverflow中文版sert(Fruit& f) throw(Full);
Fruit& remove() throw(Empty);
protected:
enum { maxSize_ = 20 };
unsigned size_;
Fruit* data_[maxSize_];
};
BagOfFruit::BagOfFruit() throw()
: size_(0)
{ }
unsigned BagOfFruit::size() const throw()
{ return size_; }
void BagOfFruit::insert(Fruit& f) throw(Full)
{
if (size_ == maxSize_) throw Full();
data_[size_++] = &f;
}
Fruit& BagOfFruit::remove() throw(Empty)
{
if (size_ == 0) throw Empty();
return *data_[--size_];
}
void insertFruitIntoBag(BagOfFruit& bag, Fruit& fruit)
{
bag.insert(fruit);
}
class BagOfApple : public BagOfFruit {
public:
BagOfApple() throw();
void insert(Apple& a) throw(Full);
Apple& remove() throw(Empty);
};
BagOfApple::BagOfApple() throw()
: BagOfFruit()
{ }
void BagOfApple::insert(Apple& a) throw(Full)
{ BagOfFruit::insert(a); }
Apple& BagOfApple::remove() throw(Empty)
{ return (Apple&) BagOfFruit::remove(); }//what does happen here?
int _tmain(int argc, _TCHAR* argv[])
{
BagOfApple bagOfApple;
Banana banana;
insertFruitIntoBag(bagOfApple, banana);
cout << "Removing an Apple from bagOfApple: ";
Apple& a2 = bagOfApple.remove();
a2.printClassName();
return 0;
}
"what does the c-style type casting do here?": Lying. Lie to the compiler, and it will get you in the end.
The meaning of the C style cast depends on what the compiler knows at
the point it occurs. If the compiler has seen the definition of
Banana
, and knows that it inherits from Fruit
, then it is
a static_cast
; if the compiler knows nothing of the relationship
between the two, it is a reinterpret_cast
. In both cases, using the
results (assuming that the actual type was Apple
) is undefined
behavior. When you explicitly cast, the compiler assumes you know what
you are doing—it takes the address of the object, and treats the
memory at that address as if it were a Banana
. If it actually is
a Banana
, everything works fine; if it isn't, you've lied to the
compiler, and it's going to come back and haunt you. The different size
is just an obvious example—if you write to something beyond the
end of Apple
, you're going to overwrite memory that doesn't belong to
the object, or possibly trigger an access violation. But even if
Apple
and Banana
have the same size, you're in undefined behavior
land, and pretty much anything could happen.
Firstly, if this code comes from the book then get another book:
- I understand it illustrates a deliberate mistake in publicly deriving a BagOfApples from a BagOfFruit (which is nonsensical because public derivation means people can continue to use the BagOfApples as a BagOfFruit, thereby inserting objects into it that are not Apples) BUT
- It is riddled with other issue and poor design:
- Ownership of fruit is never taken by the bag containers... they accept references to the objects then take their address. By not having
insert()
take a pointer, they're falsely implying the object is copied by value. This user interface is prone to application-crashing erroneous usage. - Stroustrup himself has said exception specifications have proven a mistake and shouldn't be used (I'll try to find a link to the interview transcript online if anyone's really intererested and can't find such themselves).
- The
remove()
method is deceptive as it returns the value. Comp Sci literature generally uses the termpop()
for this.
- Ownership of fruit is never taken by the bag containers... they accept references to the objects then take their address. By not having
but the question is what actually happens at this line:
{ return (Apple&) BagOfFruit::remove(); }//what does happen here?
Well, remove()
"pops" a fruit from the bag/container, and the C-style cast simply promises the compiler that that popped fruit is an Apple
. That's only likely to be correct if the bag is used exclusively through the BagOfApples-specific interface, but given it publicly derives from BagOfFruit it is entirely possible for some code to use it as a BagOfFruit and insert some other type of fruit into there. If an Apple&
is being returned but the object is not an Apple
, and someone attempts to operate on the supposed Apple
, then you'll have undefined behaviour.
In practice, this code will probably work as expected for most real-world compiler implementations. But, let's say apple adds a "const char*" member to store the region where it's grown. Lets say you go to print or compare the region but the object's really a Banana
: the compiler's likely to reinterpret the bits with values for ar[0]
and perhaps ar[1]
(for 32 bit ints and 64 bit systems) as a const char*
, then try to use the string at that non-sensical address. This is very likely to cause a segmentation fault and crash your program. But remember that even if that doesn't sound like it'd bite you with your exact usage, the behaviour's technically undefined and may be worse.
what does the c-style type casting do here?
It does nothing. I mean, you are only change the reference type to your concrete object. If you do a wrong cast, there will be some problems (even if the compiler doesn't complain about it). You should avoid c-style cast in C++. If you want to cast a base object reference to a derived class reference, you need to inspect the type at runtime. Have a look here. It's a good tutorial.
so though the remove function of the banana object is accessed by the apple object because the apple's function's address offset would be matching with that of banana's
No.
where does it lie in memory for the apple& reference here,what happens to the address space of the casted banana object(actual object)?
Nothing. Casts don't change anything in this case because it's a cast to a reference type, so it creates a reference. . They just tell the compiler to not complain about types. If you need a c-style cast in C++ you are probably doing something wrong.
精彩评论