Любопытно повторяющийся шаблон - Не можете создать более 1 производного класса? - PullRequest
0 голосов
/ 07 сентября 2018

Я застрял, используя этот шаблон, потому что создается только один из созданных мной производных классов. Проверено с g ++ и MSVS. В частности, создается только первый производный класс, который я определяю. Никаких предупреждений компилятором не выдается. Полный код указан ниже.

#include <iostream>

static int nodes = 0;

class TreeNode {
private:
    int m_id;
public:
    TreeNode() : 
        m_id(++nodes)
    {}
    TreeNode(int id) :
        m_id(id)
    {
        ++nodes;
    }
    TreeNode* left;
    TreeNode* right;

    int getId() const {
        return m_id;
    }
};


template<typename T>
//typename std::enable_if<std::is_base_of<TreeParser, T>::value>::type
class TreeParser {
protected:
    TreeParser() {
        ++parsers;
    }
public:
    static uint32_t parsers;
    void preorderTraversal(TreeNode* node) {
        if (node != nullptr) {
            processNode(node);
            preorderTraversal(node->left);
            preorderTraversal(node->right);
        }
    }
    virtual ~TreeParser() = default;

    void processNode(TreeNode* node) {              // 2, 3. the generic algorithm is customized by derived classes
        static_cast<T*>(this)->processNode(node);   // depending on the client's demand - the right function will be called
    }
};

template<class T>
uint32_t TreeParser<T>::parsers = 0;

class SpecializedTreeParser1 : public TreeParser<SpecializedTreeParser1> // 1. is-a relationship
{
public:
    explicit SpecializedTreeParser1() : 
        TreeParser()
    {}
    void processNode(TreeNode* node) {
        std::cout << "Customized (derived - SpecializedTreeParser1) processNode(node) - "
            "id=" << node->getId() << '\n';
    }
};

class SpecializedTreeParser2 : public TreeParser<SpecializedTreeParser2> // 1. is-a relationship
{
public:
    explicit SpecializedTreeParser2() : 
        TreeParser()
    {}
    void processNode(TreeNode* node) {
        std::cout << "Customized (derived - SpecializedTreeParser2) processNode(node) - "
            "id=" << node->getId() << '\n';
    }
};


int main() 
{
    TreeNode root;
    TreeNode leftChild;
    TreeNode rightChild;

    root.left = &leftChild;
    root.right = &rightChild;

    std::cout << "Root id: " << root.getId() << '\n';
    std::cout << "Left child id: " << leftChild.getId() << '\n';
    std::cout << "Right child id: " << rightChild.getId() << '\n';

    SpecializedTreeParser1 _1;
    _1.preorderTraversal(&root);

    SpecializedTreeParser2 _2;
    _2.preorderTraversal(&root);
}

Вывод:

Root id: 1
Left child id: 2
Right child id: 3
Customized (derived - SpecializedTreeParser1) preorderTraversal() - id=1
Customized (derived - SpecializedTreeParser1) preorderTraversal() - id=2
Customized (derived - SpecializedTreeParser1) preorderTraversal() - id=1963060099 // what is that?

Почему я не могу создать экземпляр второго производного класса?

Ответы [ 2 ]

0 голосов
/ 07 сентября 2018

Проблема в SpecializedTreeParser2 _2;. TreeParser leftChild и rightChild инициализируются нормально, печатается их id. Проверьте вывод

О, радость неопределенного поведения ...

Нет, TreeParser leftChild и rightChild не инициализируются нормально, и проблема не в SpecializedTreeParser2 _2.

Проблема в том, что TreeNode::left; и TreeNode* right; используются неинициализированными. Это приводит к тому, что ваша программа имеет неопределенное поведение, а красота неопределенного поведения заключается в том, что вся программа имеет неопределенное поведение, а не только использование неинициализированных переменных. Это означает, что хотя SpecializedTreeParser1 _1 кажется, что все в порядке, на самом деле это не так. Вывод, который вы видите - это поведение с неопределенным поведением. Нет смысла анализировать, откуда он взялся, почему _1 работает, а _2 - нет. Это неопределенное поведение. Исправьте это и не пытайтесь понять, почему это специфическое поведение.


Позвольте мне показать вам, что я имею в виду на более коротком примере:

int main()
{
    int a = 24;
    std::cout << a << std::endl;

    int b; // uninitialized
    std::cout << b << std::endl; // <-- this line makes the WHOLE program to have UB
}

Теперь, каковы возможные варианты поведения вышеуказанной программы?

print "24"
print <garbage>

можно и разумно ожидать

print "24"
print "0"

также возможно

print "24"
<crash>

тоже не удивительно

print <garbage>
print "24"

также возможно ... подождите что? Да!! Неопределенная природа программы не должна проявляться в строке std::cout << b << std::endl. Все выполнение программы не определено. Все что угодно может случиться!

Все это также возможно:

<crash>
print "0"
print "0"
print "24"
print "24"
print "24"
print "0"
<crash>
print "Here be dragons"
0 голосов
/ 07 сентября 2018

Я не ошибаюсь, ваша программа имеет неопределенное поведение. Указатели left и right для leftChild и rightChild остаются неинициализированными, поэтому, как только preorderTraversal() попадет туда, ваша программа будет взорвана. Вот почему в конце вы получаете странный id: он читает из какой-то случайной ячейки памяти ...

Чтобы решить эту проблему, убедитесь, что left и right члены TreeNode всегда инициализируются в nullptr, как, вероятно, ожидает остальная часть вашего кода:

class TreeNode {
    …
    TreeNode* left = nullptr;
    TreeNode* right = nullptr;
    …
};
...