【问题标题】:How to reduce the implementation code of lots of wrapper classes?如何减少大量包装类的实现代码?
【发布时间】:2020-02-19 10:45:35
【问题描述】:

我正在开发一个包含一些类的库,我们称它们为C1, C2 and ... Cn。这些类中的每一个都实现了一些接口,即I1, I2, ... Im. (n > m)。库中对象之间的关系很复杂,我不得不 为我的库用户提供一些 API 以使用智能指针访问这些对象。

经过一番讨论,我发现将共享指针返回给库用户并不是一个好主意,因为在这种情况下,我无法确保可以在我的库内存中精确删除对象。返回弱指针也有同样的问题,因为如果 API .lock()s 的用户使用弱指针并将结果共享指针保存在某处,我将再次面临同样的问题。

我的最后一个想法是为弱指针公开某种包装器。包装类可以是这样的:

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
};

如您所见,所有这些包装类都共享相同的实现。减少所有这些包装类的代码的最佳方法是什么?有没有办法避免重复类似的代码?

【问题讨论】:

  • 你的意思是“重构”吗?
  • @dandan78 不,我编辑了帖子标题。
  • 元编程(接口名称可以是静态的)或桥接设计模式应该不错。
  • @seccpur 有什么例子吗?
  • @Gupta 这可能与__declspec(propety 一起实现......这可以允许getter(结帐)和setter(签入)同时仍然制作'。'可能的语法 - 这至少需要 clang 或 msvc

标签: c++ c++11 shared-ptr smart-pointers weak-ptr


【解决方案1】:

在不使用宏的情况下,您可以做的最好的事情是修复这些重复:

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);
}

你甚至可以用它制作宏

【讨论】:

  • 看起来不错,谢谢。似乎所有方法的实现都是不可避免的,对吧?
  • @Gupta 是的,根据某些方案自动生成方法是不可能的。正如我所说,您需要 cpp 中的静态反射或静态代码生成器
【解决方案2】:

如果您在包装器中删除继承,您可能会执行以下操作来分解所有包装器:

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.");
       }
   }
};

【讨论】:

  • 我必须承认这比我的还要好;)
  • 哇,看起来很棒。
  • shared_ptr&lt;int&gt; stolen; void func(Wrapper&lt;int&gt; ptr) { stolen = ptr.operator-&gt;(); }
  • @firda:尽量防范墨菲,而不是马基雅维利 :-)。
【解决方案3】:

对树/图形节点使用智能指针并不理想。树节点析构函数会破坏指向子节点的智能指针,而这些智能指针又会调用子节点析构函数,从而导致递归,当树很深或可用堆栈大小较小时,可能会溢出堆栈。

另一种设计是有一个树类来管理其节点的生命周期并使用普通指针,a-la std::map。并且有一个规则,移除一个节点会使指向被移除子树的指针和引用失效。

这样的设计简单、健壮且在运行时最高效。

【讨论】:

  • 我在我的机器上创建了一个大二叉树,有 24 层和1.67772e+07 节点,每个节点包含两个共享指针。这是我可以在笔记本电脑上创建的最大树,因为 25 级它无法为共享指针分配内存。销毁时, a 需要一些时间,但不会溢出堆栈。原因很清楚,因为调用嵌套析构函数的栈顶激活记录不会超过 24 条。
  • @Gupta 有了平衡的树,你就可以侥幸逃脱。
  • 但是你是对的,如果我的树的深度超过 750(对于不平衡的树),我会在销毁时出现堆栈溢出。好点。谢谢;)
猜你喜欢
  • 2020-06-12
  • 2021-08-29
  • 1970-01-01
  • 2012-01-03
  • 2022-01-19
  • 2020-10-17
  • 2018-12-08
  • 2011-09-04
  • 1970-01-01
相关资源
最近更新 更多