【问题标题】:Why static_cast conversion from base to derived works inside base but not outside为什么从基础到派生的 static_cast 转换在基础内部而不在外部有效
【发布时间】:2020-03-22 21:57:35
【问题描述】:

为什么从基类到派生的 static_case 转换在基类内部有效,但在基类外部无效

#include <iostream>
using std::cout;

class Base
{
public:
    template <typename T>
    int getValue() const { return static_cast<const T&>(*this).getValue(); }
};

class Derived: public Base
{
public:
    Derived(int v): value(v) { }
    int getValue() const { return value; }
    int value;
};

class Another
{
    int getValue() const { return 5; }
};

template <typename T>
void out(const Base & base) {
    cout << base.getValue<T>() << '\n';
}

int main() {
    Derived d(5);
    Base b;
    out<Derived>(d);    //understandable, d has derived part.
    out<Derived>(b);   //don't understand, b is only base.
    out<Another>(b);    //compile time error   
    //static_cast<Derived>(b);   //compile time error
}

我阅读了this 关于 CRTP 的文章并偶然发现了这段代码:

template <typename T>
class Base
{
public:
    void doSomething()
    {
        T& derived = static_cast<T&>(*this);
        use derived...
    }
};

class Derived : public Base<Derived>
{
    ...
};

我也不清楚这里的转换是如何工作的。

【问题讨论】:

    标签: c++ templates type-conversion static-cast


    【解决方案1】:

    static_cast 转换仅在此转换合法时才可使用。在您的代码中,您正在创建类Base 的对象,并且您正试图将其转换为类Derived。幸运的是,Derived::getValue() 的实现不使用任何数据成员,而是从文字返回一个值。无论如何,这是未定义的行为。

    在 CRTP 的情况下,不会创建 Base 类的实例:仅使用 Derived 的实例。

    更新。试试这个:

    //static_cast<Derived>(b);   doesn't compile
    static_cast<Derived&>(b);   shall compile
    

    更新 2。您会收到垃圾邮件,因为 Derived::getValue() 使用了数据成员(在您的代码的初始版本中未使用数据成员)。

    【讨论】:

    • 我编辑了代码 sn-p。请具体化你的答案。我对 static_cast 不会导致任何编译时错误这一事实感到困惑
    • 那么,CRTP其实就是强制转换为派生类型来使用它的部分实现,而static_cast只是一个实现,而不是一个特性?只是使用派生类实现的基类?
    • @АлександрКушниренко 我不确定您所说的“不是功能”是什么意思,但是是的,这是实现的一部分。仅当 Derived 实际上继承自 Base&lt;Derived&gt; 时,这才有效。
    • @Dmitty Kuzminov 非常感谢您,感谢您的耐心等待。我想通了这个概念,只是将 static_cast 误解为一种“瘦”实现技术。
    【解决方案2】:

    这是 C++ 规则的一部分。 static_cast 可用于将基类表达式转换为派生类。如果在运行时,该对象实际上不是派生类对象的基类子对象,则它是未定义的行为,无需诊断。

    你问题的第一句话是不正确的,这个演员表可以写在代码的任何地方。

    out&lt;Another&gt;() 编译失败,因为AnotherBase 没有继承关系。

    【讨论】:

      【解决方案3】:

      main() 中的最后一次转换在语法上不正确,并且不等同于模板中的代码,您不能将对象向上转换为对象(您可以向下转换,导致类型收缩)。在上面的模板中,您可以转换引用。

      Derived&amp;可以绑定Base&amp;static_cast没办法查。 CRTP 确保了这一点,因为 this 指向 Derived 类型的存储,*this 导致可以安全地转换为 Derived&amp; 引用对象的引用。

      Base 不是Another 的基类时,对Another 的引用不能绑定到对Base 的引用。在这种情况下,使用static_cast 转换指针或引用是非法的。

      模板代码是合法的,如果 CRTP 有效,因为模板代码在 Derived 足够完整的类型(即使用模板的地方)被实例化。模板本身不生成任何东西,也不编译,只检查正确性。

      不过,在 CRTP 中,有些事情是不可能的,例如使用派生类的基类嵌套类型声明作为完整类型,原因很简单:与成员变量和函数不同,它们不完整且不受前向查找的影响。如果需要这样的使用,则必须在 Base 之前定义第三种类型,包含所需的声明。

      【讨论】:

      • 不,程序编译并输出。我不明白为什么这种转换在 Base 类内部是可能的,但在它外部是非法的。
      • @АлександрКушниренко 你不理解我。它不在类内部或外部,它是模板和非模板上下文之间的区别。第二次尝试将 Base 转换为 Derived 会导致读取非法内存存储,而最后一次是完全非法的,因为您试图将自动对象向上转换为对象,npt 引用引用
      • 第三次尝试导致编译错误,但第二次没有,我不明白原因。
      • @АлександрКушниренко 原因是 Base 不是 Derived。
      • Base 不是 Another,但 out 是编译错误,而 out 编译并给出未定义的行为。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多