我遇到过这种句法结构几次,我想知道:
- 这是做什么的?
- 设计推理可能是什么?
它往往看起来像这样:
struct SubType : public SomeSuperType {
SubType(int somthing) : SuperType(something), m_foo(*((FooType *)0))
{}
private:
FooType m_foo;
}
需要明确的是,代码有效。但目的是什么?m_foo
没有那条线会是什么状态?
我遇到过这种句法结构几次,我想知道:
它往往看起来像这样:
struct SubType : public SomeSuperType {
SubType(int somthing) : SuperType(something), m_foo(*((FooType *)0))
{}
private:
FooType m_foo;
}
需要明确的是,代码有效。但目的是什么?m_foo
没有那条线会是什么状态?
此构造的目的是SomeType
在您正式需要一个对象但不想要或不能声明一个真实对象的情况下模拟一个假的未命名类型对象。它有其有效的用途,不一定会导致未定义的行为。
一个经典的例子是确定某个类成员的大小
sizeof (*(SomeClass *) 0).some_member
或 decltype 的类似应用
decltype((*(SomeClass *) 0).some_member)
上述示例都不会导致任何未定义的行为。在未评估的上下文中,like*(SomeClass *) 0
是完全合法和有效的。
您还可以在语言标准本身中看到这种技术用于说明目的,如 8.3.5/12
trailing-return-type 对于在 declarator-id 之前指定更复杂的类型最有用:
template <class T, class U> auto add(T t, U u) -> decltype(t + u);
而不是
template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);
观察(*(T*)0) + (*(U*)0)
下如何使用表达式对类型和之间decltype
的二元运算符的结果类型进行编译时预测。+
T
U
当然,同样,这些技巧仅在用于非评估上下文时才有效,如上所示。
有时它被用作“空引用”的初始化器,如
SomeType &r = *(SomeType *) 0;
但这实际上跨越了合法的边界并产生了未定义的行为。
您在特定示例中的内容是无效的,因为它试图在评估的上下文中访问无效的“空左值”。
PS 在 C 语言中,还有规范的特殊部分,即运算符&
和*
相互取消,这意味着它&*(SomeType *) 0
是有效的并且保证评估为空指针。但它没有扩展到 C++。
这是做什么的?未定义的行为。
设计推理可能是什么?导致未定义行为的愿望。没有其他理由。
我认为这个例子不一定是UB。这取决于 的定义FooType
。假设Foo
是一个空类,它的构造函数做了一些事情:
class Foo {
public:
Foo() { std::cout << "Hey, world! A new Foo just arrived.\n"; }
// I think the default copy and assign constructors do nothing
// with an empty type, but just in case:
Foo(const Foo&) {}
Foo& operator=(const Foo&) { return *this; }
};
现在,假设我需要一个 Foo,无论出于何种原因,我不想触发构造函数。这样做不会导致任何实际的取消引用,因为operator* 不会取消引用并且复制构造函数不使用其引用参数:
Foo(*static_cast<Foo*>(0));