当我玩类继承和智能指针的一些侧面时,我发现了一些我不理解的现代C++类型转换。我相信有一个合乎逻辑的解释,希望有人可以提供它。
请考虑以下类:
class base
{
public:
virtual ~base() = default;
void Func() const {}
};
class derived : public base
{
private:
using base::Func; // makes base::Func inaccessible
};
class derived
中的"使用"使得通过derived*
访问base::Func
变得不可能,但通过base*
仍然可以访问该功能。
现在,智能指针开始发挥作用。当然,base:Func
具有相同的可访问性规则。
using namespace std;
shared_ptr<derived> pShDer{ make_shared<derived>() };
// error C2248: 'derived::Func': cannot access private member declared in class 'derived'
//pShDer->Func();
((shared_ptr<base>&)pShDer)->Func(); // ok
// error C2440: 'static_cast': cannot convert from 'std::shared_ptr<derived>' to 'std::shared_ptr<base> &'
//static_cast<shared_ptr<base>&>(pShDer)->Func();
reinterpret_cast<shared_ptr<base>&>(pShDer)->Func(); // ok
static_cast<shared_ptr<base>const &>(pShDer)->Func(); // ok
reinterpret_cast<shared_ptr<base>const &>(pShDer)->Func(); // ok
这个想法是,我不构造一个临时shared_ptr<base>
并在代码行结束时销毁它,而是我希望编译器将现有shared_ptr<derived>
视为指向 base 的指针。
与unique_ptr
一起玩游戏会稍微改变情况:
unique_ptr<derived> pUnDer{ make_unique<derived>() };
// error C2248: 'derived::Func': cannot access private member declared in class 'derived'
//pUnDer->Func();
((unique_ptr<base>&)pUnDer)->Func(); // ok
// error C2440: 'static_cast': cannot convert from 'std::unique_ptr<derived,std::default_delete<derived>>' to 'std::unique_ptr<base,std::default_delete<base>> &'
// static_cast and safe_cast to reference can only be used for valid initializations or for lvalue casts between related classes
//static_cast<unique_ptr<base>&>(pUnDer)->Func();
reinterpret_cast<unique_ptr<base>&>(pUnDer)->Func(); // ok
// error C2440: 'static_cast': cannot convert from 'std::unique_ptr<derived,std::default_delete<derived>>' to 'const std::unique_ptr<base,std::default_delete<base>> &'
// Reason: cannot convert from 'std::unique_ptr<derived,std::default_delete<derived>>' to 'const std::unique_ptr<base,std::default_delete<base>>'
// No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
//static_cast<unique_ptr<base>const&>(pUnDer)->Func();
reinterpret_cast<unique_ptr<base>const&>(pUnDer)->Func(); // ok
显然,C型演员总是有效,reinterpret_cast
也是如此。但是为什么static_case
只在一种情况下编译(而不是在所有情况下或没有)?(Microsoft)编译器试图通过错误消息后面的提示告诉我什么?
这背后没有用例,只是一个想要了解的人:-)谢谢!
汉斯
reinterpret_cast<shared_ptr<base>&>(pShDer)->Func(); // ok
未定义的行为。您正在指示编译器将 glvalue 视为shared_ptr<derived>
的 glvalue 视为 glvalue toshared_ptr<base>
。通过与引用对象的实际类型不相似(即仅在const
限定条件上有所不同)进行的成员访问会导致未定义的行为。
reinterpret_cast<shared_ptr<base>const &>(pShDer)->Func(); // ok
出于与上述相同的原因,也未定义的行为。
((shared_ptr<base>&)pShDer)->Func(); // ok
也是未定义的行为,因为它等效于reinterpret_cast<shared_ptr<base>&>(pShDer)->Func();
.
static_cast<shared_ptr<base>const &>(pShDer)->Func(); // ok
这个还可以。它创建一个临时shared_ptr<base>
对象,通过构造函数调用初始化,static_cast<shared_ptr<base>const &>(pShDer)
引用此临时对象。
std::shared_ptr
有一个构造函数,如果D*
可以隐式转换为B*
,则允许从std::shared_ptr<D>
构造std::shared_ptr<B>
。这是完全安全的。shared_ptr
完全支持此用例。销毁最后一个shared_ptr
时,它将始终在构造原始shared_ptr
时使用的指针类型上调用delete
。(请注意,相比之下,unique_ptr
通过unique_ptr
实例的实际删除程序类型进行删除。
//static_cast<shared_ptr<base>&>(pShDer)->Func();
失败,因为不允许非const
左值引用绑定到临时对象。因此,static_cast
也指定不允许等效转换为引用类型。
这个想法是,我不构造一个临时shared_ptr并在代码行结束时销毁它,
在static_cast
的情况下,你正是这样做的,这是一件好事。
而是我希望编译器将现有shared_ptr视为指向 base 的指针。
这基本上是不允许的,并且将始终具有UB。
((unique_ptr<base>&)pUnDer)->Func(); // ok
reinterpret_cast<unique_ptr<base>&>(pUnDer)->Func(); // ok
reinterpret_cast<unique_ptr<base>const&>(pUnDer)->Func(); // ok
所有这些未定义的行为都与shared_ptr
相同的原因。
//static_cast<unique_ptr<base>&>(pUnDer)->Func();
由于与shared_ptr
相同的原因不起作用。
//static_cast<unique_ptr<base>const&>(pUnDer)->Func();
不起作用,因为您正在尝试复制unique_ptr
(通过创建临时对象)。unique_ptr
是不可复制的。
但是,它是可移动的,因此static_cast<unique_ptr<base>const&>(std::move(pUnDer))->Func();
可以工作,但会导致所有权转移到临时unique_ptr
实例,然后在完整表达式结束时销毁该实例,并随之销毁托管derived
对象。如果您的base
没有virtual
析构函数,这也将具有 UB。
总而言之:如果您不是 100% 确定您了解它在特定用例中的作用,请不要使用reinterpret_cast
。否则会导致 UB。切勿使用 C 型石膏,因为它们有时可能会解析为reinterpret_cast
(或其他石膏),有时很难判断它们是否安全。