【问题标题】:Why doesn't implicit conversion occur on a typecasted nullptr为什么在类型转换的 nullptr 上不会发生隐式转换
【发布时间】:2019-11-09 00:02:26
【问题描述】:

我试图找到一种静态断言派生类指针可以安全地重新解释为指向基类的指针的方法,但遇到了意想不到的事情:

我预计以下代码中的两个静态断言会失败。

class Base1 {
    int x;
};
class Base2 {
    int y;
};
class Derived : public Base1, public Base2 {
    int z;
};

// one of these should fail???
static_assert( (Derived *)nullptr == (Base1 *)nullptr, "err1" );
static_assert( (Derived *)nullptr == (Base2 *)nullptr, "err2" );
// and one of these as well???
static_assert( (Base1 *)(Derived *)nullptr == nullptr, "err3" );
static_assert( (Base2 *)(Derived *)nullptr == nullptr, "err3" );

对于第一对断言,由于它的参数类型为 Derived*Base1*/Base2*,我预计 Derived* 会隐式转换为 Base1*Base2*比较。由于Base1Base2 不能占用相同的内存,因此它们不能都位于Derived 对象占用的内存的开头,因此其中一个转换应该增加了指针值。那么不应该发现非零指针等于null。

同样,对于第二对,我希望显式转换为 Base1*Base2* 应该改变了其中一个指针,但它们仍然比较等于 null。

这是怎么回事?

【问题讨论】:

  • 这就像问为什么int a = 0; long b = 0; static_assert(a == b); 有效。你所有的点都等于nullptr,所以它们应该都是一样的。
  • 我认为 nullptr 总是相同的值。并且铸造它们在这种情况下没有效果。见stackoverflow.com/a/44117520/7834933
  • 但是,如果这些不是 nullptr,这将产生预期的效果。

标签: c++ multiple-inheritance static-assert


【解决方案1】:

空指针始终是空指针,并且始终是空指针。

[conv.ptr](强调我的)

3 类型为“指向 cv D”的纯右值,其中 D 是一个类 type,可以转换为“pointer to cv B”类型的纯右值,其中 BD 的基类。如果B 是不可访问的或模棱两可的 D 的基类,需要这种转换的程序是 格式不正确。转换的结果是一个指向基数的指针 派生类对象的类子对象。 空指针值 转换为目标类型的空指针值

由于将Derived*Base* 进行比较需要将它们转换为通用类型,因此这就是将要发生的转换。如您所见,它保留了 null 值。

这样做的基本原理是不可能从空指针值产生看起来有效的值。如果您从空指针值开始,您将继续使用它。

【讨论】:

  • 我会 (nullptr)+1 但那是 UB。
【解决方案2】:

输入C-style cast

让我们回顾一下下面的引言:

当遇到 C 风格的转换表达式时,编译器会尝试将其解释为以下转换表达式,顺序如下: 一)const_cast<new_type>(expression) b) static_cast<new_type>(expression)

如果你尝试使用reinterpret_castit will fail compilation,因为reinterpret_cast 不是一个常量表达式,但static_cast 不仅仅是类型系统的转换。在这种情况下,static_cast 可能会告诉编译器根据 Base1Base2Derived 的(在编译时已知的)大小执行一些偏移,但 static_cast may involve implicit conversions, a call to the constructor of new_type or a call to a user-defined conversion operator.

此外,来自static_cast 的文本还声明了以下内容:

2) 如果 new_type 是指向某个类 D 的指针或引用,并且表达式的类型是指向其非虚基 B 的指针或引用,则 static_cast 执行向下转型。如果 B 是 D 的模棱两可、不可访问或虚基(或虚基的基),则这种向下转换是不正确的。

9) 指向某个类 D 成员的指针可以向上转换为指向 其明确的、可访问的基类 B 的成员。这个 static_cast 不检查以确保该成员实际存在于运行时中 指向对象的类型。

这实际上是因为Derived 的布局是完全已知的,虽然此布局在技术上是实现定义的,但编译器精确地知道此布局,因为Base1Base2 都不是不可访问的或虚拟基址。

我们实际上可以通过提供不可访问的基础来看到失败的情况:

class Base{};

class Base1 : public Base
{
    int x;
};

class Base2 : public Base
{
    int y;
};

class Derived 
    : public Base1
    , public Base2 
{
    int z;
};

constexpr static Derived d{};

constexpr static const Derived* derived_ptr = &d;
constexpr static const Base1* base1_ptr = &d;
constexpr static const Base2* base2_ptr = &d;

// fail due to inaccessible base
static_assert(static_cast<const Derived*>(nullptr) == static_cast<const Base*>(nullptr), "err1" ); // fails
static_assert(static_cast<const Derived*>(derived_ptr) == static_cast<const Base*>(nullptr), "err2" ); // fails

// succeed
static_assert(static_cast<const Derived*>(derived_ptr) == static_cast<const Base1*>(base1_ptr), "err3" );
static_assert(static_cast<const Derived*>(derived_ptr) == static_cast<const Base2*>(base2_ptr), "err4" );
// and one of these as well???
static_assert(static_cast<const Base1*>(static_cast<const Derived*>(derived_ptr)) == base1_ptr, "err5" );
static_assert(static_cast<const Base2*>(static_cast<const Derived*>(derived_ptr)) == base2_ptr, "err6" );

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-30
    • 2019-09-16
    • 1970-01-01
    • 2014-03-30
    • 1970-01-01
    • 2012-10-17
    • 2020-02-10
    • 2017-11-25
    相关资源
    最近更新 更多