使用求值为None类型的弱指针
Using weak pointers that evaluate to a None type
在我将python集成到c++应用程序的实现中,我添加了对可能有效也可能无效的节点的支持。在内部,这些是作为弱指针存储的,因此我考虑使用isValid()方法,用户可以在调用公开的方法之前使用该方法。如果在无效节点上调用已暴露的方法,则会抛出异常。
然而,我想知道是否有可能比这更蟒化一点。是否有可能在调用暴露的方法之前内部检查指针是否有效,如果它没有使python对象为None?
我想要的一个例子是:
>>> my_valid_node = corelibrary.getNode("valid_node")
>>> my_valid_node.printName()
valid_node
然而,现在系统中的其他地方可能会使节点无效,但从python的角度来看,我希望节点变为None。
>>> my_valid_node.printName()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'printName'
谁能想到一个方法来做到这一点?
当外部事件发生时,没有干净的方法使对对象的引用成为对None
的引用。然而,当使用python接口时,可以:
- 实现
__nonzero__
方法,允许对象在布尔上下文中计算。 - 当
weak_ptr
锁定失败时抛出Python异常。一个简单的解决方案是访问默认构造的boost::python::object
上的成员属性,因为它引用了None
。
请注意,属性查找自定义点,如__getattr__
,将不够,因为weak_ptr
所指向的对象可能在属性访问和分配到c++成员函数之间过期。
下面是基于上述细节的一个完整的最小示例。在这个例子中,spam
和spam_factory
(一个实例化由shared_ptr
管理的spam
对象的工厂)被认为是遗留类型。spam_proxy
辅助类通过weak_ptr
和其他辅助函数引用spam
,帮助使遗留类型适应Python。
#include <string>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/weak_ptr.hpp>
#include <boost/python.hpp>
/// Assume legacy APIs.
// Mockup class containing data.
class spam
{
public:
explicit spam(const char* name)
: name_(name)
{}
std::string name() { return name_; }
private:
std::string name_;
};
// Factory for creating and destroying the mockup class.
class spam_factory
{
public:
boost::shared_ptr<spam> create(const char* name)
{
instance_ = boost::make_shared<spam>(name);
return instance_;
}
void destroy()
{
instance_.reset();
}
private:
boost::shared_ptr<spam> instance_;
};
/// Auxiliary classes and functions to help obtain Pythonic semantics.
// Helper function used to cause a Python AttributeError exception to
// be thrown on None.
void throw_none_has_no_attribute(const char* attr)
{
// Attempt to extract the specified attribute on a None object.
namespace python = boost::python;
python::object none;
python::extract<python::object>(none.attr(attr))();
}
// Mockup proxy that has weak-ownership.
class spam_proxy
{
public:
explicit spam_proxy(const boost::shared_ptr<spam>& impl)
: impl_(impl)
{}
std::string name() const { return lock("name")->name(); }
bool is_valid() const { return !impl_.expired(); }
boost::shared_ptr<spam> lock(const char* attr) const
{
// Attempt to obtain a shared pointer from the weak pointer.
boost::shared_ptr<spam> impl = impl_.lock();
// If the objects lifetime has ended, then throw.
if (!impl) throw_none_has_no_attribute(attr);
return impl;
}
private:
boost::weak_ptr<spam> impl_;
};
// Use a factory to create a spam instance, but wrap it in the proxy.
spam_proxy spam_factory_create(
spam_factory& self,
const char* name)
{
return spam_proxy(self.create(name));
}
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Expose the proxy class as if it was the actual class.
python::class_<spam_proxy>("Spam", python::no_init)
.def("__nonzero__", &spam_proxy::is_valid)
.add_property("name", &spam_proxy::name)
;
python::class_<spam_factory>("SpamFactory")
.def("create", &spam_factory_create) // expose auxiliary method
.def("destroy", &spam_factory::destroy)
;
}
互动用法:>>> import example
>>> factory = example.SpamFactory()
>>> spam = factory.create("test")
>>> assert(spam.name == "test")
>>> assert(bool(spam) == True)
>>> if spam:
... assert(bool(spam) == True)
... factory.destroy() # Maybe occurring from a C++ thread.
... assert(bool(spam) == False) # Confusing semantics.
... assert(spam.name == "test") # Confusing exception.
...
Traceback (most recent call last):
File "<stdin>", line 5, in <module>
AttributeError: 'NoneType' object has no attribute 'name'
>>> assert(spam is not None) # Confusing type.
有人可能会说,虽然接口是python的,但对象的语义不是。由于weak_ptr
语义在Python中不太常见,因此通常不会期望局部变量引用的对象被析构。如果需要weak_ptr
语义,那么考虑引入一种方法,允许用户通过上下文管理器协议在特定上下文中获得共享所有权。例如,下面的模式允许对象的有效性检查一次,然后在有限的范围内得到保证:
>>> with spam: # Attempt to acquire shared ownership.
... if spam: # Verify ownership was obtained.
... spam.name # Valid within the context's scope.
... factory.destroy() # spam is still valid.
... spam.name # Still valid.
... # spam destroyed once context's scope is exited.
下面是前面示例的完整扩展,其中spam_proxy
实现了上下文管理器协议:
#include <string>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/weak_ptr.hpp>
#include <boost/python.hpp>
/// Assume legacy APIs.
// Mockup class containing data.
class spam
{
public:
explicit spam(const char* name)
: name_(name)
{}
std::string name() { return name_; }
private:
std::string name_;
};
// Factory for creating and destroying the mockup class.
class spam_factory
{
public:
boost::shared_ptr<spam> create(const char* name)
{
instance_ = boost::make_shared<spam>(name);
return instance_;
}
void destroy()
{
instance_.reset();
}
private:
boost::shared_ptr<spam> instance_;
};
/// Auxiliary classes and functions to help obtain Pythonic semantics.
// Helper function used to cause a Python AttributeError exception to
// be thrown on None.
void throw_none_has_no_attribute(const char* attr)
{
// Attempt to extract the specified attribute on a None object.
namespace python = boost::python;
python::object none;
python::extract<python::object>(none.attr(attr))();
}
// Mockup proxy that has weak-ownership and optional shared ownership.
class spam_proxy
{
public:
explicit spam_proxy(const boost::shared_ptr<spam>& impl)
: shared_impl_(),
impl_(impl)
{}
std::string name() const { return lock("name")->name(); }
bool is_valid() const { return !impl_.expired(); }
boost::shared_ptr<spam> lock(const char* attr) const
{
// If shared ownership exists, return it.
if (shared_impl_) return shared_impl_;
// Attempt to obtain a shared pointer from the weak pointer.
boost::shared_ptr<spam> impl = impl_.lock();
// If the objects lifetime has ended, then throw.
if (!impl) throw_none_has_no_attribute(attr);
return impl;
}
void enter()
{
// Upon entering the runtime context, guarantee the lifetime of the
// object remains until the runtime context exits if the object is
// alive during this call.
shared_impl_ = impl_.lock();
}
bool exit(boost::python::object type,
boost::python::object value,
boost::python::object traceback)
{
shared_impl_.reset();
return false; // Do not suppress the exception.
}
private:
boost::shared_ptr<spam> shared_impl_;
boost::weak_ptr<spam> impl_;
};
// Use a factory to create a spam instance, but wrap it in the proxy.
spam_proxy spam_factory_create(
spam_factory& self,
const char* name)
{
return spam_proxy(self.create(name));
}
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Expose the proxy class as if it was the actual class.
python::class_<spam_proxy>("Spam", python::no_init)
.def("__nonzero__", &spam_proxy::is_valid)
// Support context manager protocol.
.def("__enter__", &spam_proxy::enter)
.def("__exit__", &spam_proxy::exit)
.add_property("name", &spam_proxy::name)
;
python::class_<spam_factory>("SpamFactory")
.def("create", &spam_factory_create) // expose auxiliary method
.def("destroy", &spam_factory::destroy)
;
}
互动用法:>>> import example
>>> factory = example.SpamFactory()
>>> spam = factory.create("test")
>>> with spam:
... assert(bool(spam) == True)
... if spam:
... assert(spam.name == "test")
... factory.destroy()
... assert(bool(spam) == True)
... assert(spam.name == "test")
...
>>> assert(bool(spam) == False)
确切的模式可能不是最python化的,但它提供了一种干净的方式来保证对象在有限范围内的生存期。
- 非类型指针和引用模板参数,以及在编译时如何/为什么解析它们.c++
- 如何访问在 c++ 中在类内声明的结构类型指针变量?
- (C )找到基本类型指针的儿童类型
- 表达式必须具有指向对象的指针类型(指针向量)
- 将子项复制构造到父类型指针中
- C 返回类型指针声明
- 调用虚拟函数而不通过类类型指针创建任何对象
- C++ 如何使用类类型指针制作向量
- 为什么基类型指针不能获取派生类对象的地址值?
- 如何在 C++ 中获取映射类型指针,映射
- 如何检查该类型的类型指针是正确对齐的
- 正在转换为短类型指针
- 指向任意类方法的模板非类型指针
- 如何通过强制转换类型指针将字符数组转换为uint16_t
- 指向shared_ptr的不透明类型 C 指针
- C++:从值类型指针强制转换为包含迭代器
- C/C++:访问给定类型指针的位置与访问另一类型指针的相同位置不同
- 比较类型指针
- 数据类型指针使用*(Datatype *)
- 如何打印c++中char类型指针的所有值