12

给定以下 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*有效吗?我似乎记得可能不是,但现在找不到具体的证据。

this此阶段的“自然”类型是Base* const,并且在某些情况下,即使this在初始化期间静态转换指针也是不行的,例如在基础构造完成之前向上转换(12.7/3)。

@DeadMG 说:

在初始化列表中获取这个的标准中有一个明确的例外。它用于将指向您自己的指针传递给子对象。

12.6.2/12 确实说:

[ 注意:因为 mem-initializer 是在构造函数的范围内计算的,所以 this 指针可以在 mem-initializer 的表达式列表中使用来引用正在初始化的对象。——尾注]

...虽然这不足以说转换Derived*为有效。

我的直觉是,在对象初始化的这个阶段,this它并没有指向一个实例,因此,严格来说,Derived即使只是一个指向它的类型的指针也是 UB。Derived*那是因为它既不是有效指针也不是空指针。

(这可能对这样的方法产生实际影响尽管在那个答案和我上面的例子中,整个事情可以通过简单地编写static_cast<Derived*>(0)来回避。)

4

1 回答 1

4

我认为是UB。

正如迈克所说:

此时,已为完整对象分配了存储空间,但仅初始化了基本子对象。因此,您只能以 C++11 中描述的“有限方式”使用对象的其余部分。

我的解释是,这不是其中一种方式。

更正式地说:

[C++11: 3.8/1]:对象的生命周期是对象的运行时属性。如果一个对象是一个类或聚合类型,并且它或它的一个成员是由一个普通默认构造函数以外的构造函数初始化的,则称该对象具有非普通初始化。[注意:通过简单的复制/移动构造函数进行初始化是非平凡的初始化。— 尾注]类型对象的生命周期T开始于:

  • 获得具有适当对齐和大小的类型T的存储,并且
  • 如果对象有非平凡的初始化,它的初始化就完成了。

和:

[C++11: 3.8/5]: 在对象的生命周期开始之前但在对象将占用的存储空间分配之后,或者在对象的生命周期结束之后并且在对象占用的存储空间被重用或释放之前,指向该存储空间的任何指针可以使用对象将要或曾经位于的位置,但只能以有限的方式使用。对于正在建造或破坏的物体,见 12.7。否则,这样的指针指的是分配的存储(3.7.4.2),并且使用指针就像指针是 type 一样void*,是明确定义的。这样的指针可能会被取消引用,但生成的左值只能以有限的方式使用,如下所述。如果出现以下情况,该程序具有未定义的行为:

  • 该对象将是或曾经是具有非平凡析构函数的类类型,并且指针用作delete-expression的操作数,
  • 指针用于访问非静态数据成员或调用对象的非静态成员函数,或
  • 指针被隐式转换(4.10)为指向基类类型的指针,或者
  • 指针用作static_cast(5.2.9) 的操作数(转换为void*、 或void*以及随后为char*、 或时unsigned char*除外),或
  • 指针用作dynamic_cast [..]的操作数
于 2013-02-04T13:19:35.900 回答