Приведение типов от базового к производному классу

У меня базовый класс:

class RedBlackTreeNode
{
// Interface is the same as the implementation
public:
    RedBlackTreeNode* left;
    RedBlackTreeNode* right;
    RedBlackTreeNode* parent;
    Color color;
    TreeNodeData* data;

    RedBlackTreeNode():
        left(0),
        right(0),
        parent(0),
        color(Black),
        data(0)
    {
    }
    // This method is to allow dynamic_cast
    virtual void foo()
    {
    }
};

и производный от него:

class IndIntRBNode : public RedBlackTreeNode
{
public:
    IndIntRBNode* left;
    IndIntRBNode* right;
    IndIntRBNode* parent;
    IndexedInteger* data;
    IndIntRBNode():
        RedBlackTreeNode(),
        left(0),
        right(0),
        parent(0),
        color(Black),
        data(0)
    {
    }
};

root () и rootHolder определены в классе RedBlackTree:

class RedBlackTree 
{
public:
    RedBlackTreeNode rootHolder;
    RedBlackTreeNode* root()
    {
        return rootHolder.left;
    }
    ...
}

Затем я пытаюсь привести к типу:

IndIntRBNode *x, *y, *z;

z = dynamic_cast<IndIntRBNode*>(root());

А «z» просто становится нулевым указателем, что означает, что приведение типа не удалось. Так что в этом плохого, как я могу исправить это, чтобы иметь возможность ссылаться на "z" как на указатель на IndIntRBNode?

Добавлено: инициализация rootHolder.left была чем-то вроде этого:

    int n = atoi(line + i);

    tree.clear();
    int j = 0;
    if (n > 100)
    {
        n = 100;        // Maximum 100 nodes
    }
    while (j < n)
    {
        IndexedInteger num(j,j + 10);
        RedBlackTreeNode* node;
        int c = tree.search(&num, tree.root(), &node);
        if (c != 0)
        { // if value is not in the tree
            IndexedInteger* v = new IndexedInteger(num);
            tree.insert(node, v, c);
            ++j;
        }
    }

Другими словами, он был инициализирован на первой итерации «while» методом «insert» таким образом:

void RedBlackTree::insert(
    RedBlackTreeNode* parentNode,
    TreeNodeData* v,
    // If it's negative, then add as the left son, else as the right
    int compare
)
{
    assert(parentNode != 0 && compare != 0);
    RedBlackTreeNode* x = new RedBlackTreeNode;
    x->data = v;
    x->parent = parentNode;
    // If it's root
    if (parentNode == &rootHolder)
    {
        // Then it must be black
        x->color = Black;
    }
    else
    {
        // Leaf must be red
        x->color = Red;
    }
    if (compare < 0)
    {
        // Insert the node as the left son
        assert(parentNode->left == NULL);
        parentNode->left = x;
    }
    else
    {
        // Insert the node as the right son
        assert(parentNode != &rootHolder && parentNode->right == NULL);
        parentNode->right = x;
    }

    ++numNodes;

    if (x != root())
    {
        rebalanceAfterInsert(x);
    }
}

На самом деле проблема заключалась в том, что "insert" динамически создает RedBlackTreeNode, поэтому это не может быть IndIntRBNode. Я действительно неправильно инициализировал его, но тогда как я могу получить базовый класс и не писать всю его реализацию с нуля, просто чтобы изменить типы? Действительно ли мне нужно переопределить все "относящиеся к типу" методы в производном классе? Это кажется очень глупым, я думаю, должен быть другой способ - что-то с наследованием классов и приведением типов, не так ли?


person Michael Pankov    schedule 13.08.2009    source источник
comment
Как был инициализирован rootHolder.left?   -  person Kirill V. Lyadvinsky    schedule 14.08.2009


Ответы (4)


Вы уверены, что RedBlackTree :: rootHolder.left инициализирован?

Я думаю, вы где-то инициализировали IndIntRBNode :: left, но когда вы обращаетесь к RedBlackTree :: rootHolder.left, вы получаете доступ к RedBlackTreeNode :: left, а это не то же поле.

person Ozan    schedule 13.08.2009
comment
Вероятно, это причина. Или, возможно, RedBlackTree :: rootHolder.left был инициализирован с неправильным типом. - person Gunther Piez; 14.08.2009
comment
Обновил вопрос: я действительно сделал, но тогда как я могу получить базовый класс и не писать всю его реализацию с нуля, просто чтобы изменить типы? - person Michael Pankov; 14.08.2009
comment
Нужно ли повторно объявлять поля в производном экземпляре с новыми типами? Реализация должна по-прежнему позволять вам хранить объекты IndIntRBNode в полях, объявленных в базе, вам просто нужно будет их понижать, когда вы хотите их использовать. - person jeremyalan; 14.08.2009
comment
Точно. Полностью избавьтесь от членов в IndIntRBNode и просто используйте те, которые уже существуют в RedBlackTreeNode. Поскольку IndIntRBNode является производным от RedBlackTreeNode, вы можете назначать объекты IndIntRBNode указателям RedBlackTreeNode. - person Remy Lebeau; 14.08.2009
comment
вы можете сохранить любого потомка RedBlackTreeNode в поле RedBlackTreeNode *. Если вы хотите ограничить IndIntRBNode только хранением IndIntRBNodes, вам необходимо написать функцию доступа RedBlackTreeNode :: SetLeft (RedBlackTreeNode * aleft), которая переопределяется в IndIntRBNode для проверки типов его аргумента. - person Ozan; 14.08.2009
comment
Вдобавок вы обнаруживаете все виды ошибок, если назначаете одно из полей базового класса и ожидаете совпадения эквивалентного поля производного класса. Имена, скорее всего, будут сбивать с толку если не вас, то всех, кто пытается во всем разобраться. - person quark; 14.08.2009

Подождите ... у вас есть производный класс с таким же именем, что и у базового класса. Как это вообще компилируется?

как инициализируется rootHolder.left? Потому что, если dynamic_cast терпит неудачу, значит, это не тип IndIntRBNode.

По сути, вы не предоставили достаточно кода, чтобы увидеть, что вы делаете неправильно, но вы ДЕЙСТВИТЕЛЬНО делаете что-то неправильно.

person Goz    schedule 13.08.2009
comment
Что вы имеете в виду, говоря об одноименном имени? Проверял код дважды, конфликтов имен нет. Но спасибо, инициализацию проверю. - person Michael Pankov; 14.08.2009
comment
у вас есть указатель на переменную с именем left в вашем базовом классе и на другую в производном классе. Тебе действительно не следует этого делать. Просто используйте те, что в вашем базовом классе. RedBlackTree p = новый IndIntRBNode; совершенно верно. - person Goz; 14.08.2009

Если я правильно читаю ваш код, вы вообще устанавливаете rootHolder.left не на IndIntRBNode указатель, а на ванильный RedBlackTreeNode.

Кроме того, вам не нужно создавать бесполезную виртуальную функцию для полиморфизма; просто объявите деструктор virtual и предоставьте пустую реализацию. Ваше использование полиморфизма также не так хорошо, поскольку члены IndIntRBNode будут скрывать члены RedBlackTreeNode, поэтому они могут указывать на разные вещи в зависимости от того, обращаетесь ли вы к ним через указатель / ссылку IndIntRBNode или указатель / ссылку RedBlackTreeNode.

person coppro    schedule 14.08.2009

Независимо от каких-либо других недостатков ваш примерный код может иметь или не иметь, он написан способом, который, кажется, неправильно понимает, как работают наследование и приведение типов. Я рекомендую вам взять книгу по C ++ по вашему выбору и прочитать ее.

В частности, вы должны знать, что каждый объект относится к одному и только одному типу. После создания объект никогда не меняет тип. Приведение указателя не преобразует объект из одного типа в другой и не создает новый объект нового типа. Приведение позволяет работать с существующим объектом с точки зрения указанного вами типа класса, но только если этот объект уже относится к этому классу или к классу, производному от него.

В том месте, где у вас сейчас есть эта строка:

z = dynamic_cast<IndIntRBNode*>(root());

попробуйте вместо этого:

RedBlackTreeNode* other_z = root();

Если other_z не равно NULL, то root () не является IndIntRBNode, и все функции dynamic_casting в мире не превратят его в один.

person Darryl    schedule 14.08.2009