我正在开发一个包含一些类的库,我们称它们为C1, C2 and ... Cn
。这些类中的每一个都实现一些接口,即I1, I2, ... Im.
(n> m)。库中对象之间的关系很复杂,我必须 为我的库用户提供一些 API,以便使用智能指针访问这些对象。
经过一些讨论,我发现将共享指针返回给库用户不是一个好主意,因为在这种情况下,我无法确保可以在库的内存中精确地删除该对象。返回弱指针有同样的问题,因为如果 API 的用户.lock()
弱指针并将结果共享指针保留在某个地方,我将再次遇到同样的问题。
我的最后一个想法是,为弱指针公开某种包装器。包装类可以是这样的:
class Wrapper_C1 : public I1
{
std::weak_ptr<C1> mC1;
public:
Wrapper_C1() = delete;
Wrapper_C1(const std::weak_ptr<C1> & c1) : mC1(c1)
{
}
int method1_C1(int x)
{
if (auto sp = mC1.lock())
{
sp->method1_C1(x);
}
else
{
throw std::runtime_error("object C1 is not loaded in the lib.");
}
}
void method2_C1(double y)
{
if (auto sp = mC1.lock())
{
sp->method2_C1(y);
}
else
{
throw std::runtime_error("object C1 is not loaded in the lib.");
}
}
// The same for other methods
};
如您所见,所有这些包装类都共享相同的实现。减少所有这些包装类代码的最佳方法是什么?有没有办法避免重复类似的代码?
如果在包装器中删除继承,则可以执行以下操作来分解所有包装器:
template <typename T>
class Wrapper
{
private:
std::weak_ptr<T> m;
public:
Wrapper() = delete;
Wrapper(const std::weak_ptr<T> & w) : m(w) {}
auto operator -> () /* const */
{
if (auto sp = m.lock())
{
return sp;
}
else
{
throw std::runtime_error("object is not loaded in the lib.");
}
}
};
在不诉诸宏的情况下,你能做的最好的事情就是修复这些重复
:if (auto sp = mC1.lock())
{
sp->method1_C1();
}
else
{
throw std::Exception("object C1 is not loaded in the lib.");
}
我所看到的,您可以轻松地将其简化为模板功能,如下所示:
template<class T, class R, class... Args>
R call_or_throw(const std::weak_ptr<T>& ptr, const std::string& error, R (T::*fun)(Args...), Args... args) {
if (auto sp = ptr.lock())
{
return std::invoke(fun, *sp, args...);
}
else
{
throw std::runtime_error(error.c_str());
}
}
比你可以这样使用它:
int method1_C1(int x)
{
return call_or_throw(mC1, "object C1 is not loaded in the lib.", &C1::method1_C1, x);
}
void method2_C1(double y)
{
return call_or_throw(mC1, "object C1 is not loaded in the lib.", &C1::method2_C1, y);
}
您甚至可以从中制作宏
对树/图节点使用智能指针不太理想。树节点析构函数会破坏指向子节点的智能指针,而这些指针反过来调用子节点析构函数,从而导致递归,当树很深或可用堆栈大小较小时,递归可能会溢出堆栈。
另一种设计是拥有一个树类来管理其节点的生存期并使用普通指针 a-lastd::map
。并有一个规则,即删除节点会使指向已删除子树的指针和引用无效。
这种设计简单、坚固且在运行时效率最高。