PAYATHON 2026

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* 是非拥有指针,只能在对象仍然存活时使用。原对象被销毁或替换后,不能继续通过它访问对象。

备注:内容仅供参考。