如何减少很多wrapper 类的实现代码?
How to reduce the implementation code of lots of wrapper classes?
我正在开发一个包含一些 classes 的库,我们称它们为 C1, C2 and ... Cn
。这些 classes 中的每一个都实现了一些接口,即 I1, I2, ... Im.
(n > m)。库中对象之间的关系很复杂,我必须
为我的图书馆用户提供一些 API 以使用智能指针访问这些对象。
经过一些讨论,我发现将共享指针返回给库用户不是一个好主意,因为在那种情况下我无法确保可以在我的库内存中准确地删除该对象。返回弱指针有同样的问题,因为如果 API .lock()
s 弱指针的用户并将结果共享指针保存在某个地方,我将再次面临同样的问题。
我的最终想法是为弱指针公开某种包装器。包装器 class 可以是这样的:
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
};
如您所见,所有这些包装器 class 都共享相同的实现。减少所有这些包装器 classes 的代码的最佳方法是什么?有没有办法避免重复类似的代码?
不求助于宏(这在这里也无济于事,要完全解决您的问题,我们需要某种静态反射)的最佳方法是修复这些重复:
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);
}
你甚至可以用它制作宏
如果您放弃包装器中的继承,您可以执行类似以下操作来分解所有包装器:
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.");
}
}
};
对 tree/graph 节点使用智能指针并不理想。树节点析构函数会破坏指向子节点的智能指针,而那些智能指针又会调用子节点析构函数,从而导致递归,当树很深或可用堆栈大小很小时,这可能会溢出堆栈。
另一种设计是让树 class 管理其节点的生命周期并使用普通指针,a-la std::map
。并有一个规则,即删除节点会使指向已删除子树的指针和引用无效。
这样的设计在 运行 时间是简单、健壮和最有效的。
我正在开发一个包含一些 classes 的库,我们称它们为 C1, C2 and ... Cn
。这些 classes 中的每一个都实现了一些接口,即 I1, I2, ... Im.
(n > m)。库中对象之间的关系很复杂,我必须
为我的图书馆用户提供一些 API 以使用智能指针访问这些对象。
经过一些讨论,我发现将共享指针返回给库用户不是一个好主意,因为在那种情况下我无法确保可以在我的库内存中准确地删除该对象。返回弱指针有同样的问题,因为如果 API .lock()
s 弱指针的用户并将结果共享指针保存在某个地方,我将再次面临同样的问题。
我的最终想法是为弱指针公开某种包装器。包装器 class 可以是这样的:
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
};
如您所见,所有这些包装器 class 都共享相同的实现。减少所有这些包装器 classes 的代码的最佳方法是什么?有没有办法避免重复类似的代码?
不求助于宏(这在这里也无济于事,要完全解决您的问题,我们需要某种静态反射)的最佳方法是修复这些重复:
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);
}
你甚至可以用它制作宏
如果您放弃包装器中的继承,您可以执行类似以下操作来分解所有包装器:
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.");
}
}
};
对 tree/graph 节点使用智能指针并不理想。树节点析构函数会破坏指向子节点的智能指针,而那些智能指针又会调用子节点析构函数,从而导致递归,当树很深或可用堆栈大小很小时,这可能会溢出堆栈。
另一种设计是让树 class 管理其节点的生命周期并使用普通指针,a-la std::map
。并有一个规则,即删除节点会使指向已删除子树的指针和引用无效。
这样的设计在 运行 时间是简单、健壮和最有效的。