第7章 智能指针集成
智能指针是现代 C++ 的重要特性,pybind11 提供了完整的智能指针支持。本章将介绍如何绑定使用智能指针的类,以及如何管理对象的生命周期。
7.1 std::unique_ptr 绑定
Section titled “7.1 std::unique_ptr 绑定”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
// 不可复制的类(只能使用 unique_ptr)class Resource {public: Resource(int id) : m_id(id) { py::print(py::str("Resource {} created").format(m_id)); }
~Resource() { py::print(py::str("Resource {} destroyed").format(m_id)); }
int id() const { return m_id; } void process() { py::print(py::str("Processing resource {}").format(m_id)); }
private: int m_id;};
PYBIND11_MODULE(unique_ptr, m) { // 使用 PYBIND11_DECLARE_HOLDER_TYPE 声明 unique_ptr 持有者类型 PYBIND11_DECLARE_HOLDER_TYPE(Resource, std::unique_ptr<Resource>);
py::class_<Resource, std::unique_ptr<Resource>>(m, "Resource") .def(py::init<int>(), py::arg("id"), "创建 Resource") .def("id", &Resource::id) .def("process", &Resource::process);
// 工厂函数返回 unique_ptr m.def("create_resource", [](int id) { return std::unique_ptr<Resource>(new Resource(id)); }, py::arg("id"), "创建并返回 Resource 的 unique_ptr");}Python 使用
Section titled “Python 使用”import unique_ptr as up
res = up.create_resource(42)print(res.id()) # 42res.process() # Processing resource 42
del res// 声明 holder 类型PYBIND11_DECLARE_HOLDER_TYPE(T, std::unique_ptr<T>);
// 绑定时指定 holder 类型py::class_<Resource, std::unique_ptr<Resource>>(m, "Resource")| 步骤 | 说明 |
|---|---|
PYBIND11_DECLARE_HOLDER_TYPE | 通知 pybind11 关于这个 holder 类型 |
std::unique_ptr<Resource> | 作为第二个模板参数传递 |
关键点: unique_ptr 表示唯一所有权,当 Python 侧的引用计数归零时,对象会被自动删除。
7.2 std::shared_ptr 绑定
Section titled “7.2 std::shared_ptr 绑定”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
class DatabaseConnection {public: DatabaseConnection(const std::string& url) : m_url(url), m_ref_count(0) { py::print(py::str("DatabaseConnection to {} created").format(m_url)); }
~DatabaseConnection() { py::print(py::str("DatabaseConnection to {} destroyed").format(m_url)); }
void query(const std::string& sql) { py::print(py::str("Executing: {}").format(sql)); }
const std::string& url() const { return m_url; }
private: std::string m_url; int m_ref_count;};
PYBIND11_MODULE(shared_ptr, m) { // 声明 shared_ptr holder 类型 PYBIND11_DECLARE_HOLDER_TYPE(DatabaseConnection, std::shared_ptr<DatabaseConnection>);
py::class_<DatabaseConnection, std::shared_ptr<DatabaseConnection>>(m, "DatabaseConnection") .def(py::init<const std::string&>(), py::arg("url")) .def("query", &DatabaseConnection::query, py::arg("sql")) .def("url", &DatabaseConnection::url);
// 工厂函数返回 shared_ptr m.def("connect", [](const std::string& url) { return std::make_shared<DatabaseConnection>(url); }, py::arg("url"), "创建数据库连接");}Python 使用
Section titled “Python 使用”import shared_ptr as sp
conn1 = sp.connect("postgresql://localhost/mydb")conn2 = conn1 # 共享引用
print(conn1.url()) # postgresql://localhost/mydbconn1.query("SELECT * FROM users")
print(conn1 is conn2) # True
del conn1
del conn2unique_ptr vs shared_ptr
Section titled “unique_ptr vs shared_ptr”| 特性 | unique_ptr | shared_ptr |
|---|---|---|
| 所有权 | 唯一 | 共享 |
| 复制 | 不可(可移动) | 可以(引用计数 +1) |
| 销毁时机 | 唯一所有者销毁时 | 最后一个所有者销毁时 |
| 开销 | 低(无引用计数) | 中等(有引用计数) |
| 线程安全 | 是(移动操作) | 是(引用计数操作) |
7.3 std::weak_ptr 处理
Section titled “7.3 std::weak_ptr 处理”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
class CacheEntry {public: CacheEntry(const std::string& key, const std::string& value) : m_key(key), m_value(value) {}
const std::string& key() const { return m_key; } const std::string& value() const { return m_value; }
private: std::string m_key; std::string m_value;};
class Cache {public: void put(const std::string& key, std::shared_ptr<CacheEntry> entry) { m_entries[key] = entry; }
// 返回 weak_ptr std::weak_ptr<CacheEntry> get(const std::string& key) { auto it = m_entries.find(key); if (it != m_entries.end()) { return std::weak_ptr<CacheEntry>(it->second); } return std::weak_ptr<CacheEntry>(); }
bool is_expired(const std::string& key) { auto wp = get(key); return wp.expired(); // 检查是否已过期(对象已销毁) }
private: std::unordered_map<std::string, std::shared_ptr<CacheEntry>> m_entries;};
PYBIND11_MODULE(weak_ptr, m) { PYBIND11_DECLARE_HOLDER_TYPE(CacheEntry, std::shared_ptr<CacheEntry>);
py::class_<CacheEntry, std::shared_ptr<CacheEntry>>(m, "CacheEntry") .def(py::init<const std::string&, const std::string&>(), py::arg("key"), py::arg("value")) .def("key", &CacheEntry::key) .def("value", &CacheEntry::value);
py::class_<Cache>(m, "Cache") .def(py::init<>()) .def("put", &Cache::put, py::arg("key"), py::arg("entry")) .def("get", &Cache::get, py::arg("key"), "返回 weak_ptr") .def("is_expired", &Cache::is_expired, py::arg("key"), "检查缓存是否过期");
// weak_ptr 不能直接返回给 Python,需要转换为 shared_ptr 或返回 bool m.def("try_get_value", [](const std::string& key, Cache& cache) -> py::object { auto wp = cache.get(key); if (auto sp = wp.lock()) { return py::make_tuple(true, sp->value()); } return py::make_tuple(false, py::none()); }, py::arg("key"), py::arg("cache"), "尝试获取缓存值");}Python 使用
Section titled “Python 使用”import weak_ptr as wp
cache = wp.Cache()
entry = wp.CacheEntry("user:1", "Alice")cache.put("user:1", entry)
cached = cache.get("user:1")print(cached) # <weak_ptr reference>
print(cache.is_expired("user:1")) # False
valid, value = wp.try_get_value("user:1", cache)print(valid) # Trueprint(value) # Alice
del entryprint(cache.is_expired("user:1")) # Trueweak_ptr 处理策略
Section titled “weak_ptr 处理策略”| 方法 | 说明 |
|---|---|
wp.expired() | 检查对象是否已销毁 |
wp.lock() | 尝试升级为 shared_ptr(失败返回空) |
| Python 端 | 不能直接暴露 weak_ptr,需要封装为可检查的形式 |
7.4 智能指针工厂函数
Section titled “7.4 智能指针工厂函数”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
class Config {public: Config() : m_verbose(false), m_port(8080) {}
void set_verbose(bool v) { m_verbose = v; } void set_port(int p) { m_port = p; }
bool verbose() const { return m_verbose; } int port() const { return m_port; }
private: bool m_verbose; int m_port;};
class Server {public: Server(std::shared_ptr<Config> config) : m_config(config) {}
void start() { py::print(py::str("Server starting on port {}").format(m_config->port())); }
std::shared_ptr<Config> config() const { return m_config; }
private: std::shared_ptr<Config> m_config;};
PYBIND11_MODULE(factory_functions, m) { PYBIND11_DECLARE_HOLDER_TYPE(Config, std::shared_ptr<Config>); PYBIND11_DECLARE_HOLDER_TYPE(Server, std::shared_ptr<Server>);
py::class_<Config, std::shared_ptr<Config>>(m, "Config") .def(py::init<>()) .def("set_verbose", &Config::set_verbose, py::arg("verbose")) .def("set_port", &Config::set_port, py::arg("port")) .def("verbose", &Config::verbose) .def("port", &Config::port);
py::class_<Server, std::shared_ptr<Server>>(m, "Server") .def(py::init<std::shared_ptr<Config>>(), py::arg("config")) .def("start", &Server::start) .def("config", &Server::config, "获取配置对象的共享引用");
// 工厂函数:创建带配置的 Server m.def("create_server", [](int port) { auto config = std::make_shared<Config>(); config->set_port(port); return std::make_shared<Server>(config); }, py::arg("port") = 8080, "创建 Server 实例");}Python 使用
Section titled “Python 使用”import factory_functions as ff
server = ff.create_server(9000)server.start() # Server starting on port 9000
config = server.config()print(config.port()) # 9000
config2 = configprint(config is config2) # True
config.set_verbose(True)print(server.config().verbose()) # True7.5 生命周期管理策略
Section titled “7.5 生命周期管理策略”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
// 场景:Widget 归 Owner 所有,Owner 销毁时 Widget 也销毁// 但 Python 代码也可能持有 Widget 引用
class Widget {public: Widget(const std::string& name) : m_name(name) { py::print(py::str("Widget '{}' created").format(m_name)); } ~Widget() { py::print(py::str("Widget '{}' destroyed").format(m_name)); } const std::string& name() const { return m_name; }
private: std::string m_name;};
class Owner {public: Owner(const std::string& name) : m_name(name) { py::print(py::str("Owner '{}' created").format(m_name)); } ~Owner() { py::print(py::str("Owner '{}' destroyed").format(m_name)); // m_widget 在这里被销毁 }
void set_widget(std::unique_ptr<Widget> w) { m_widget = std::move(w); }
Widget* widget() const { return m_widget.get(); }
private: std::string m_name; std::unique_ptr<Widget> m_widget;};
PYBIND11_MODULE(lifetime_management, m) { PYBIND11_DECLARE_HOLDER_TYPE(Widget, std::unique_ptr<Widget>);
py::class_<Widget, std::unique_ptr<Widget>>(m, "Widget") .def(py::init<const std::string&>(), py::arg("name")) .def("name", &Widget::name) .def("__repr__", [](const Widget& w) { return py::str("<Widget '{}'>").format(w.name()); });
py::class_<Owner, std::unique_ptr<Owner>>(m, "Owner") .def(py::init<const std::string&>(), py::arg("name")) .def("set_widget", &Owner::set_widget, py::arg("widget"), "设置内部 Widget") .def("widget", &Owner::widget, py::arg("owner"), "获取 Widget 指针(不转移所有权)", py::return_value_policy::reference_internal) .def("__repr__", [](const Owner& o) { return py::str("<Owner '{}'>").format(o.name()); });}Python 使用
Section titled “Python 使用”import lifetime_management as lm
owner = lm.Owner("manager")print(owner) # <Owner 'manager'>
widget = lm.Widget("button")owner.set_widget(widget)
w = owner.widget()print(w) # <Widget 'button'>
del owner
owner2 = lm.Owner("manager2")widget2 = lm.Widget("slider")owner2.set_widget(widget2)del widget2 # 不影响 Ownerdel owner2 # Widget 仍然被销毁生命周期策略
Section titled “生命周期策略”| 策略 | pybind11 选项 | 说明 |
|---|---|---|
| C++ 拥有所有权 | return_value_policy::automatic | 默认,引用计数管理 |
| 共享所有权 | shared_ptr<T> | Python 和 C++ 共享 |
| C++ 拥有,Python 借用 | return_value_policy::reference_internal | 短期借用,不延长生命周期 |
| C++ 拥有,Python 借用 | return_value_policy::reference | 不修改引用计数 |
// 返回引用(不转移所有权).def("get_widget", &Owner::widget, py::return_value_policy::reference_internal)
// 返回智能指针(共享所有权).def("get_shared_widget", &Owner::shared_widget, py::return_value_policy::automatic) // 默认7.6 循环引用问题
Section titled “7.6 循环引用问题”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
// 循环引用示例:// Parent 持有 Child 引用// Child 可能持有 Parent 引用(通过 weak_ptr 避免循环)
class Child;
class Parent {public: Parent(const std::string& name) : m_name(name) {}
void add_child(std::shared_ptr<Child> child) { m_children.push_back(child); // child 可能持有 weak_ptr 指向 this child->set_parent(py::cast(std::weak_ptr<Parent>(shared_from_this()))); }
const std::string& name() const { return m_name; } size_t num_children() const { return m_children.size(); }
private: std::string m_name; std::vector<std::shared_ptr<Child>> m_children;};
class Child {public: Child(const std::string& name) : m_name(name) {}
void set_parent(std::weak_ptr<Parent> parent) { m_parent = parent; }
std::string parent_name() const { if (auto p = m_parent.lock()) { return p->name(); } return "(no parent)"; }
const std::string& name() const { return m_name; }
private: std::string m_name; std::weak_ptr<Parent> m_parent; // 使用 weak_ptr 避免循环引用};
PYBIND11_MODULE(circular_refs, m) { PYBIND11_DECLARE_HOLDER_TYPE(Parent, std::shared_ptr<Parent>); PYBIND11_DECLARE_HOLDER_TYPE(Child, std::shared_ptr<Child>);
py::class_<Parent, std::shared_ptr<Parent>>(m, "Parent") .def(py::init<const std::string&>(), py::arg("name")) .def("add_child", &Parent::add_child, py::arg("child")) .def("name", &Parent::name) .def("num_children", &Parent::num_children);
py::class_<Child, std::shared_ptr<Child>>(m, "Child") .def(py::init<const std::string&>(), py::arg("name")) .def("name", &Child::name) .def("parent_name", &Child::parent_name);}Python 使用
Section titled “Python 使用”import circular_refs as cr
parent = cr.Parent("Dad")child = cr.Child("Son")
print(parent.name()) # Dadprint(child.name()) # Son
parent.add_child(child)
print(child.parent_name()) # Dadprint(parent.num_children()) # 1
del parentprint(child.parent_name()) # (no parent) - weak_ptr 已过期print(child.name()) # Son - 对象仍然存在循环引用避免策略
Section titled “循环引用避免策略”循环引用(A → B → A): Parent 持有 shared_ptr<Child> → Child 持有 shared_ptr<Parent> → 内存泄漏
解决方案:1. Child 使用 weak_ptr<Parent>(推荐)2. 或者手动 break 循环(Python 端调用 clear())| 方法 | 优点 | 缺点 |
|---|---|---|
weak_ptr<Parent> | 自动避免循环 | 需要 lock() 检查 |
| 手动清理 | 精确控制 | 需要 Python 端配合 |
| 避免双向引用 | 简单 | 限制设计 |
本章介绍了 pybind11 智能指针集成的核心概念:
| 主题 | 关键点 |
|---|---|
| unique_ptr | PYBIND11_DECLARE_HOLDER_TYPE + std::unique_ptr<T> |
| shared_ptr | 共享所有权,引用计数管理 |
| weak_ptr | 避免循环引用,需要 lock() 检查 |
| 工厂函数 | 返回智能指针的工厂函数模式 |
| 生命周期策略 | return_value_policy 控制所有权转移 |
| 循环引用 | 使用 weak_ptr 打破循环 |
最佳实践:
- 默认使用
unique_ptr(开销最低)- 需要共享时使用
shared_ptr- 避免循环引用用
weak_ptr- 理解
return_value_policy的四种模式
下一章我们将讨论 pybind11 的高级主题,包括异常处理、性能优化和自定义类型转换。