что на самом деле происходит во время приведения типов в стиле c в отношении адресного пространства приведенных объектов? - PullRequest
3 голосов
/ 09 мая 2011

Согласно книге C ++ FAQ от Cline, следующий код описывает неправильное наследование.обратите внимание, что я могу добавить банан, который является своего рода фруктом, в bagofApples, который на самом деле должен содержать только яблоки ... но из-за отношения наследования банан добавляется в bagofApples.происходит в этой строке:

 { return (Apple&) BagOfFruit::remove(); }//what does happen here?

что здесь делает приведение типов в стиле c?обратите внимание, что sizeof (банан) равен 4004, а у яблока - только 4. поэтому, хотя к функции удаления объекта банана обращается объект apple, потому что смещение адреса функции яблока будет совпадать с смещением адреса функции банана, но когдабанан типизирован по отношению к яблочному объекту, что происходит с элементом данных 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 ]

4 голосов
/ 09 мая 2011

«Что здесь делает приведение типов в стиле c?»: Ложь.Обмани компилятору, и он в конце концов поможет тебе.

Значение приведения в стиле C зависит от того, что знает компилятор в тот момент, когда он происходит.Если компилятор видел определение Banana и знает, что он наследует от Fruit, то это static_cast;если компилятор ничего не знает об отношениях между ними, это reinterpret_cast.В обоих случаях использование результатов (при условии, что фактический тип был Apple) является неопределенным поведением.Когда вы явно приводите, компилятор предполагает, что вы знаете, что делаете, - он берет адрес объекта и обрабатывает память по этому адресу, как если бы это был Banana.Если это на самом деле Banana, все работает нормально;если это не так, вы солгали компилятору, и он вернется и будет преследовать вас.Другой размер - это просто очевидный пример - если вы записываете что-то за пределами Apple, вы собираетесь перезаписать память, которая не принадлежит объекту, или, возможно, вызвать нарушение доступа.Но даже если Apple и Banana имеют одинаковый размер, вы находитесь на неопределенной территории поведения, и почти все может произойти.

3 голосов
/ 09 мая 2011

Во-первых, если этот код взят из книги, тогда получите другую книгу:

  • Я понимаю, что это иллюстрирует преднамеренную ошибку в публичном извлечении BagOfApples из BagOfFruit (что бессмысленно, поскольку публичное получение означает, что люди могут продолжать использовать BagOfApples как BagOfFruit, тем самым вставляя в него объекты, которые являются не яблоки) НО
  • Это пронизано другой проблемой и плохим дизайном:
    • Владение фруктами никогда не захватывается контейнерами с пакетами ... они принимают ссылки на объекты, а затем принимают их адрес. Не имея 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*, затем попытайтесь использовать строку по этому бессмысленному адресу. Это может привести к ошибке сегментации и крушению вашей программы. Но помните, что даже если это не звучит так, как будто вас укусят от вашего точного использования, поведение технически не определено и может быть хуже.

1 голос
/ 09 мая 2011

что здесь делает приведение типов в стиле c?

Ничего не происходит.Я имею в виду, вы только меняете тип ссылки на ваш конкретный объект.Если вы сделаете неправильное приведение, возникнут некоторые проблемы (даже если компилятор не жалуется на это).Вы должны избегать приведения в стиле C в C ++.Если вы хотите преобразовать ссылку на базовый объект в ссылку на производный класс, вам необходимо проверить тип во время выполнения.Посмотрите здесь .Это хороший учебник.

, так что хотя функция удаления бананового объекта доступна для объекта apple, потому что смещение адреса функции яблока будет совпадать с таковым для

No. * 1013 банана*

где он находится в памяти для яблока и ссылки здесь, что происходит с адресным пространством отлитого бананового объекта (фактического объекта)?

Ничего.Приведения типов ничего не меняют в этом случае, потому что это приведение к ссылочному типу, поэтому оно создает ссылку.,Они просто говорят компилятору не жаловаться на типы.Если вам нужен приведение в стиле C в C ++, вы, вероятно, делаете что-то не так.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...