C++ 如何通过基类 unique_ptr 调用派生类方法?
可以通过 basePtr.get() 取得裸指针,将它转换为 Derived* 后调用派生类方法。get() 不会转移所有权,basePtr 仍然管理原来的对象。
题目中的代码还需要解决两个问题:basePtr 尚未指向对象;Base 没有虚析构函数,默认的 std::unique_ptr<Base> 无法安全地删除 Derived 对象。
如何转换指针并调用方法
确认 basePtr 指向有效的 Derived 对象后,可以这样调用:
if (basePtr) {
static_cast<Derived*>(basePtr.get())->Method();
}
这段代码分为三步:
get()返回被管理的Base*,所有权仍由unique_ptr持有。static_cast<Derived*>将基类指针向下转换为派生类指针。- 通过转换后的指针调用
Method()。
整个过程不会改变 basePtr 的类型,也不会创建另一个所有者。
if (basePtr) 只检查指针是否为空,static_cast 不做运行时类型检查。程序必须保证该指针确实指向 Derived 对象中的 Base 子对象。如果实际对象只是 Base,这样的向下转换会导致未定义行为。
原代码为什么不能直接使用
原代码中的声明:
std::unique_ptr<Base> basePtr;
只创建了一个空的智能指针,没有创建 Base 或 Derived 对象。调用成员函数前,必须先让它管理一个有效对象。
basePtr->Method() 也无法编译。basePtr 提供的是 Base*,编译器会在 Base 中查找 Method(),但这个类没有该成员。
下面的写法虽然创建了对象,却仍然存在销毁问题:
// 对题目中的类定义,这种所有权安排存在问题。
std::unique_ptr<Base> basePtr(new Derived);
std::unique_ptr<Base> 默认使用 std::default_delete<Base>,最终通过 Base* 执行 delete。题目中的 Base 没有虚析构函数,通过它删除 Derived 对象会导致未定义行为。即使两个类都没有额外资源,这种写法也不安全。
不修改类定义的完整写法
可以使用自定义删除器,让智能指针保存 Base*,销毁时通过 Derived* 删除对象:
#include <iostream>
#include <memory>
class Base
{
public:
Base() {}
};
class Derived : public Base
{
public:
Derived() {}
void Method()
{
std::cout << "Derived method" << std::endl;
}
};
struct DerivedDeleter
{
void operator()(Base* ptr) const noexcept
{
delete static_cast<Derived*>(ptr);
}
};
int main()
{
std::unique_ptr<Base, DerivedDeleter> basePtr(new Derived);
if (basePtr) {
static_cast<Derived*>(basePtr.get())->Method();
}
// 后续仍可取得 Base*,对象所有权继续由 basePtr 管理。
Base* base = basePtr.get();
(void)base;
return 0;
}
这种写法无需修改 Base 和 Derived,也保留了 unique_ptr 的独占所有权和自动释放行为。前提是这个智能指针只管理按上述方式创建的 Derived 对象。如果让它接管普通的 Base 对象,删除器中的向下转换就不成立。
示例中的 std::unique_ptr<Base, DerivedDeleter> 与使用默认删除器的 std::unique_ptr<Base> 是不同类型。如果接口要求必须使用后者,在题目给定的类定义下,单靠指针转换无法解决安全销毁 Derived 对象的问题。
为什么不能直接使用 dynamic_cast
对于题目中的类,下面的向下转换无法编译:
auto* derived = dynamic_cast<Derived*>(basePtr.get());
这种 dynamic_cast 要求源类型 Base 是多态类型,也就是至少包含一个虚函数。题目中的 Base 没有虚函数,不满足这个条件。
如果允许修改类定义,可以给 Base 添加虚析构函数。这样就能使用默认的 std::unique_ptr<Base> 管理 Derived,同时通过 dynamic_cast 检查运行时类型。在不修改类定义的限制下,则需要使用有明确类型保证的 static_cast。
保留独占所有权时要注意什么
临时调用派生类方法只需要 get()。不要为此调用 release(),因为它会解除原智能指针的所有权。也不要把 get() 返回的指针交给另一个拥有所有权的智能指针,否则可能重复释放对象。
转换得到的 Derived* 是非拥有指针,只能在对象仍然存活时使用。原对象被销毁或替换后,不能继续通过它访问对象。
备注:内容仅供参考。
