关于铸造对象的地址空间的c风格类型转换期间实际发生了什么?

时间:2011-05-09 07:39:14

标签: c++ inheritance casting

根据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;
}

3 个答案:

答案 0 :(得分:4)

“c式铸造在这里做什么?”:说谎。骗到了 编译器,它最终会帮助你。

C样式转换的含义取决于编译器所知道的内容 这一点发生了。如果编译器已经看到了定义 Banana,并知道它继承自Fruit,然后就是。{ 一个static_cast;如果编译器不知道这种关系 在两者之间,它是reinterpret_cast。在这两种情况下,使用 结果(假设实际类型为Apple)未定义 行为。显式强制转换时,编译器假定您知道什么 你正在做 - 它需要对象的地址,并对待 该地址的内存就像是Banana一样。如果确实如此 一个Banana,一切正常;如果不是,你就骗了 编译器,它会回来困扰你。不同的大小 只是一个明显的例子 - 如果你写的东西超出了 在Apple结束时,你将覆盖不属于的内存 对象,或可能触发访问冲突。但即便如此 AppleBanana具有相同的大小,您处于未定义的行为中 土地,几乎任何事情都可能发生。

答案 1 :(得分:3)

首先,如果此代码来自本书,那么请另外写一本书:

  • 据我所知,它说明了从BagOfFruit公开派生BagOfApples的一个故意错误(这是荒谬的,因为公共派生意味着人们可以继续使用BagOfApples 作为 BagOfFruit,从而将对象插入其中不是苹果)
  • 它充满了其他问题和糟糕的设计:
    • 水果的所有权永远不会被包装容器占用......他们接受对物品的引用然后拿走他们的地址。通过没有insert()获取指针,他们错误地暗示该对象是按值复制的。此用户界面易于应用程序崩溃错误使用。
    • Stroustrup本人已经说过异常规范已被证明是错误的,不应该使用(如果有人真正感兴趣并且找不到这样的话,我会尝试在线找到面试记录的链接。)
    • 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风格的演员,你可能会做错事。