尝试CRTP。为什么会产生奇怪的结果?

时间:2019-04-12 19:59:07

标签: c++

这是基本的CRTP代码。

运行此代码时,输​​出为“世界”而不是“ hello”。这是为什么?

#include <iostream>

template<typename Derived>
struct A {
    void foo()
    {
        static_cast<Derived*>(this)->bar();
    }
};

struct B : A<B>{
    void bar()
    {
        std::cout << "hello" << std::endl;
    }
};

struct C : A<C> {
    void bar()
    {
        std::cout << "world" << std::endl;
    }
};

int main() {
    void* b = new B();
    auto c = static_cast<A<C>*>(b);
    c->foo();
    return 0;
}

我正在使用gcc 5.4

主要问题是A<B>A<C>是不同的类型。我想将它们存储在向量中,并像使用虚拟函数的多态继承类型一样使用它。我试图查看将发生void*存储的情况,但我不知道发生了什么。

3 个答案:

答案 0 :(得分:3)

void* b = new B();
auto c = static_cast<A<C>*>(b);
c->foo();

这将产生未定义的行为。您正在通过指向不相关类型B的指针来访问类型A<C>的对象。这违反了严格的别名。

如果要在向量中存储指向不同对象类型的指针,则应使用常规的旧virtual函数和继承。 CRTP用于采用通用功能并将其应用于不同类型。 not 不能将两种不同的类型视为相同。

答案 1 :(得分:2)

您不需要CRTP即可查看示例中的内容。让我们简化一下:

#include <iostream>


struct B {
    void bar()
    {
        std::cout << "hello" << std::endl;
    }
};

struct C {
    void bar()
    {
        std::cout << "world" << std::endl;
    }
};

int main() {
    void* b = new B();
    auto c = static_cast<C*>(b);
    c->bar();
    return 0;
}

这将打印world。我们只是在C实例上调用了B方法。这怎么可能呢?使用void*可以绕过某些规则,但是这些规则仍然存在。您不应将实例视为不是实例。如果这样做,您将获得不确定的行为。

答案 2 :(得分:2)

  

主要问题是A<B>A<C>是不同的类型。我想将它们存储在向量中,并像使用虚拟函数的多态继承类型一样使用它。

您可以通过定义可以作为类模板A的基类的常规类来实现。

struct Base 
{
    virtual ~Base{} {}
    virtual void foo() = 0;
};

template<typename Derived>
struct A : Base {
    void foo() override
    {
        static_cast<Derived*>(this)->bar();
    }
};

现在您可以使用:

std::vector<Base*> baseObjects;
baseObjects.push_back(new B());
baseObjects.push_back(new C());

baseObjects[0]->foo();
baseObjects[1]->foo();