当我研究类继承和智能指针的一些方面时,我发现了一些我不理解的关于现代 C++ 类型转换的东西。我确定有一个合乎逻辑的解释,希望有人能提供。
考虑这些课程:
class base
{
public:
virtual ~base() = default;
void Func() const {}
};
class derived : public base
{
private:
using base::Func; // makes base::Func inaccessible
};
class derived
中的“使用”使得通过base::Func
访问derived*
是不可能的,但是通过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>
视为一个指向基的指针。
和
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 到 shared_ptr<base>
一样。通过与引用对象的实际类型不similar(即仅在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
有一个构造函数,如果 std::shared_ptr<B>
可以隐式转换为 std::shared_ptr<D>
,则允许从 D*
构造一个 B*
。这是完全安全的。 shared_ptr
正好支持这个用例。当最后一个 shared_ptr
被销毁时,它总是会在构造原始 delete
的指针类型上调用 shared_ptr
。 (请注意,相比之下,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
(或其他转换),有时很难判断它们是否安全。