给出以下CRTP示例:
template <typename T>
int foo(T* const)
{
return 0;
}
template <typename Derived>
struct Base
{
Base() : bar(foo(static_cast<Derived*>(this)) {};
int bar;
};
struct Derived1 : Base<Derived1> {};
此处this
到Derived*
的转换是否有效?我似乎记得它可能不存在,但现在却找不到具体证据。
&#34;自然&#34;此阶段this
的类型为Base* const
,并且在某些情况下,即使在初始化期间静态转换this
指针也不行,例如 upcasting 在基地建设完成之前(12.7 / 3)。
@DeadMG说:
标准w.r.t中有明确的例外情况。在初始化列表中获取此信息。将指针传递给自己的子对象。
12.6.2 / 12确实说:
[注意:因为mem-initializer是在构造函数的范围内计算的,所以可以在mem-initializer的表达式列表中使用this指针来引用正在初始化的对象。 - 后注]
...虽然这还不足以说转换为Derived*
是有效的。
我的直觉是,在对象初始化的这个阶段,this
使不指向Derived
的实例,因此,甚至只要拥有指向它的类型为Derived*
的指针,严格来说就是UB。那是因为它既不是有效指针也不是空指针。
(这可能会对this等方法产生实际影响,但在上面的答案和上面的例子中,只需简单地编写static_cast<Derived*>(0)
就可以完成整个过程。)
答案 0 :(得分:4)
我认为是UB。
正如迈克所说:
此时,已为整个对象分配存储空间,但仅初始化了基础子对象。因此,您只能以C ++ 11中描述的“有限方式”使用对象的其余部分。
这是我的解释,这不是其中之一。
更正式:
[C++11: 3.8/1]:
对象的生命周期是对象的运行时属性。 如果一个对象属于类或聚合类型,并且它或其成员之一由除了普通默认构造函数之外的构造函数初始化,则称该对象具有非平凡的初始化。 [注意:通过简单的复制/移动构造函数进行初始化是非平凡的初始化。 - 结束记录] 类型T
的对象的生命周期始于:
- 获得具有
T
类型的正确对齐和大小的存储,并且- 如果对象具有非平凡的初始化,则其初始化已完成。
和
[C++11: 3.8/5]:
在对象的生命周期开始之前但是在对象占用的存储空间已经分配之后,或者在对象的生命周期结束之后和存储之前重用或释放被占用的对象,可以使用任何指向对象所在或存在的存储位置的指针,但只能以有限的方式使用。对于正在构建或破坏的对象,请参见12.7 。除此以外, 这样的指针指的是已分配的存储(3.7.4.2),并且使用指针就像指针类型为void*
一样,是明确定义的。这样的指针可以被解除引用,但是所得到的左值可以仅以有限的方式使用,如下所述。 如果出现以下情况,该程序会有未定义的行为:
- 该对象将是或具有非平凡析构函数的类类型,并且该指针用作 delete-expression 的操作数,
- 指针用于访问非静态数据成员或调用对象的非静态成员函数,或
- 指针被隐式转换(4.10)为指向基类类型的指针,或
- 指针用作
static_cast
(5.2.9)的操作数(转换为void*
或void*
时除外)随后转到char*
或unsigned char*
)或- 指针用作
的操作数dynamic_cast
[..]