根据Cline的C ++ FAQ,下面的代码描述了不正确的继承。 请注意,我可以将一种香蕉添加到bagofApples中,它实际上只包含苹果..但由于继承关系,香蕉被添加到bagofApples中。
但问题是这一行实际发生了什么:
{ return (Apple&) BagOfFruit::remove(); }//what does happen here?
c风格的铸造在这里做什么? 请注意,sizeof(香蕉)是4004,而苹果的大小只有4。 因此虽然apple对象访问了banana对象的remove函数,因为apple的函数的地址偏移量将与banana的匹配,但是当香蕉被类型化为苹果对象时,数据成员ar [1000]会发生什么。香蕉? 苹果和它的记忆在哪里?在这里引用,铸造香蕉对象(实际对象)的地址空间会发生什么?
Apple& a2 = bagOfApple.remove();
完整的代码如下。
#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 insert(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;
}
答案 0 :(得分:4)
“c式铸造在这里做什么?”:说谎。骗到了 编译器,它最终会帮助你。
C样式转换的含义取决于编译器所知道的内容
这一点发生了。如果编译器已经看到了定义
Banana
,并知道它继承自Fruit
,然后就是。{
一个static_cast
;如果编译器不知道这种关系
在两者之间,它是reinterpret_cast
。在这两种情况下,使用
结果(假设实际类型为Apple
)未定义
行为。显式强制转换时,编译器假定您知道什么
你正在做 - 它需要对象的地址,并对待
该地址的内存就像是Banana
一样。如果确实如此
一个Banana
,一切正常;如果不是,你就骗了
编译器,它会回来困扰你。不同的大小
只是一个明显的例子 - 如果你写的东西超出了
在Apple
结束时,你将覆盖不属于的内存
对象,或可能触发访问冲突。但即便如此
Apple
和Banana
具有相同的大小,您处于未定义的行为中
土地,几乎任何事情都可能发生。
答案 1 :(得分:3)
首先,如果此代码来自本书,那么请另外写一本书:
insert()
获取指针,他们错误地暗示该对象是按值复制的。此用户界面易于应用程序崩溃错误使用。remove()
方法具有欺骗性,因为它返回值。 Comp Sci文献通常使用术语pop()
。但问题是这一行实际发生了什么:
{ return (Apple&) BagOfFruit::remove(); }//what does happen here?
好吧,remove()
“弹出”来自包/容器的水果,而C风格的演员只是向编译器承诺弹出的水果是Apple
。如果袋子仅通过BagOfApples特定的界面使用,那么这只是正确的,但鉴于它公开来自BagOfFruit,一些代码完全有可能将它用作BagOfFruit并将一些其他类型的水果插入其中。如果正在返回Apple&
但对象不是Apple
,并且有人试图对假定的Apple
进行操作,那么您将有未定义的行为。
实际上,对于大多数实际的编译器实现,此代码可能会按预期工作。但是,让我们说苹果增加了一个“const char *”成员来存储它成长的地区。让我们说你打印或比较区域,但对象真的是Banana
:编译器可能会重新解释具有ar[0]
和ar[1]
值的位(对于32位整数和64位)位系统)作为const char*
,然后尝试在该非敏感地址使用该字符串。这很可能会导致分段错误并导致程序崩溃。但请记住,即使这听起来并不像是用你的确切用法咬你,这种行为在技术上是未定义,可能会更糟。
答案 2 :(得分:1)
c风格的铸造是做什么的 这里吗?
它什么都不做。我的意思是,您只是将引用类型更改为具体对象。如果你做了错误的演员,会有一些问题(即使编译器没有抱怨它)。 你应该避免在C ++中进行c风格的转换。如果要将基础对象引用强制转换为派生类引用,则需要在运行时检查类型。看看here。这是一个很好的教程。
所以尽管apple对象访问了banana对象的remove函数,因为apple的函数的地址偏移量将与banana的匹配
没有
苹果和苹果的记忆在哪里?在这里引用,铸造香蕉对象(实际对象)的地址空间会发生什么?
无。在这种情况下,强制转换不会更改任何内容,因为它是对引用类型的强制转换,因此它会创建引用。 。他们只是告诉编译器不要抱怨类型。如果你需要用C ++进行c风格的演员,你可能会做错事。