CRTP 和向下转型
CRTP and downcasting
跟进 中关于向下转换和类型安全的类似问题,我想知道以下示例是否会产生未定义的行为。
已创建 Base class 实例。没有发生动态绑定。
但是,在 Base::interface 函数中,Base class 的实例被转换为 Derived class 的实例。
这样安全吗?如果是,为什么?
请在下面找到这段代码。
#include <iostream>
template <typename Derived>
struct Base{
void interface(){
static_cast<Derived*>(this)->implementation();
}
};
struct Derived1: Base<Derived1>{
void implementation(){
std::cout << "Implementation Derived1" << std::endl;
}
};
int main(){
std::cout << std::endl;
Base<Derived1> d1;
d1.interface();
std::cout << std::endl;
}
没有 Derived
这个新的 Derived *
可以指向,所以它绝对不安全:演员表本身有未定义的行为,如 [expr.static.cast] 中所述§ 11(强调我的):
A prvalue of type “pointer to cv1 B
”, where B
is a class type, can be converted to a prvalue of type “pointer to cv2 D
”, where D
is a complete class derived from B
, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...] If the prvalue of type “pointer to cv1 B
” points to a B
that is actually a subobject of an object of type D
, the resulting pointer points to the enclosing object of type D
. Otherwise, the behavior is undefined.
您可以通过限制对 Base
的构造函数的访问来减轻这种风险:
template <typename Derived>
struct Base{
// Same as before...
protected:
Base() = default;
};
这样更好,但如果有人不小心定义了 struct Derived2 : Base<AnotherDerived> { };
,仍然会出现同样的问题。可以通过特定的 friend
声明来防止这种情况发生,但缺点是会完全访问 Base
的私有成员:
template <typename Derived>
struct Base{
// Same as before...
private:
friend Derived;
Base() = default;
};
请注意,这仍然允许 Derived
在其成员函数中构造裸 Base<Derived>
对象,但这是我通常停止打地鼠的地方。
跟进
已创建 Base class 实例。没有发生动态绑定。
但是,在 Base::interface 函数中,Base class 的实例被转换为 Derived class 的实例。
这样安全吗?如果是,为什么?
请在下面找到这段代码。
#include <iostream>
template <typename Derived>
struct Base{
void interface(){
static_cast<Derived*>(this)->implementation();
}
};
struct Derived1: Base<Derived1>{
void implementation(){
std::cout << "Implementation Derived1" << std::endl;
}
};
int main(){
std::cout << std::endl;
Base<Derived1> d1;
d1.interface();
std::cout << std::endl;
}
没有 Derived
这个新的 Derived *
可以指向,所以它绝对不安全:演员表本身有未定义的行为,如 [expr.static.cast] 中所述§ 11(强调我的):
A prvalue of type “pointer to cv1
B
”, whereB
is a class type, can be converted to a prvalue of type “pointer to cv2D
”, whereD
is a complete class derived fromB
, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...] If the prvalue of type “pointer to cv1B
” points to aB
that is actually a subobject of an object of typeD
, the resulting pointer points to the enclosing object of typeD
. Otherwise, the behavior is undefined.
您可以通过限制对 Base
的构造函数的访问来减轻这种风险:
template <typename Derived>
struct Base{
// Same as before...
protected:
Base() = default;
};
这样更好,但如果有人不小心定义了 struct Derived2 : Base<AnotherDerived> { };
,仍然会出现同样的问题。可以通过特定的 friend
声明来防止这种情况发生,但缺点是会完全访问 Base
的私有成员:
template <typename Derived>
struct Base{
// Same as before...
private:
friend Derived;
Base() = default;
};
请注意,这仍然允许 Derived
在其成员函数中构造裸 Base<Derived>
对象,但这是我通常停止打地鼠的地方。