【问题标题】:Curiously Recurring Template Pattern - Can't create more than 1 derived class?奇怪地重复出现的模板模式 - 不能创建超过 1 个派生类?
【发布时间】:2018-09-06 21:51:39
【问题描述】:

我坚持使用这种模式,因为我创建的派生类中只有一个被实例化。用 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?

为什么我不能实例化第二个派生类?

【问题讨论】:

  • 你有没有想过使用调试器? TreeNode 的左侧或右侧似乎没有初始化。

标签: c++ templates inheritance c++14 static-polymorphism


【解决方案1】:

我没记错,你的程序有未定义的行为。 leftChildrightChildleftright 指针未初始化,因此一旦 preorderTraversal() 到达那里,您的程序就会崩溃。这也是为什么你最终会得到奇怪的id:它正在从某个随机内存位置读取……

要解决此问题,请确保 TreeNodeleftright 成员始终初始化为 nullptr,正如您的其余代码所期望的那样:

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

【讨论】:

  • 添加到这个正确的答案,只需将子指针初始化为空
  • 我确实应该为我的答案添加一个解决方案。感谢您指出了这一点;固定。
【解决方案2】:

问题是SpecializedTreeParser2 _2;TreeParser leftChildrightChild 已初始化,它们的 id 已打印。检查输出

哦,未定义行为的乐趣...

不,TreeParser leftChildrightChild 没有初始化好,问题不是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 &lt;&lt; b &lt;&lt; std::endl 行中体现出来。程序的整个执行是未定义的。 任何事情都可能发生!

所有这些都是可能的:

<crash>
print "0"
print "0"
print "24"
print "24"
print "24"
print "0"
<crash>
print "Here be dragons"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多