通过隐式类型转换模拟接口

Simulating an interface by implicit typecast

本文关键字:模拟 接口 类型转换      更新时间:2023-10-16

假设我有几个类A1, A2, A3,它们的数据可以存储在同一个POD数据结构D中。这些类在如何管理数据方面有所不同,例如,在何处分配动态数据结构。然而在语义上,它们表示所有相同类型的数据。

我的A?类之一被设计为D的包装器,它可以基于现有的D而不复制它。

现在我想以统一的方式实现所有A?类的读访问。我不想使用虚方法,也不想模板化所有使用A?类的代码。

下面的设计合理吗?

这个模式有名字吗?(Facade)

有明显的陷阱吗?

/* in practice, D is large or should not be copied for other reasons */
struct D { int * mymember; }
struct ACRef {
   ACRef (D const & d) : m_dataref (&d) { }
   /* operations for A-like classes */
   int getMyMember () const { return *(m_dataref->mymember); }
private:
   D const * m_dataref;
};
struct A1 {
   /* A1 stuff, manages m_data.mymember in a particular way */
   // implicit conversion to ACRef possible
   // kind of "is a" relationship: an A1 "is an" ACRef
   operator ACRef () { return ACRef {this->m_data}; }
private:
   D m_data;
};
struct A2 {
   explicit A2 (D & d) : m_data (&d) { }
   /* A2 stuff, manages m_data.mymember in a particular way */
   // implicit conversion to ACRef possible
   // kind of "is a" relationship: an A2 "is an" ACRef
   operator ACRef () { return ACRef {*(this->m_data)}; }
private:
   D * m_data;
};
/* A3 defined similar to A1 */
/* function that should operate on A?'s */
int printMyMember (ACRef a) {
   std::cout << a.getMyMember () << std::endl;
}
A1 a1;
A2 a2;
// ...
printMyMember (a1);
printMyMember (a2);    

我认为这种方法的唯一缺点是最终会有多个隐式共享状态的对象。因此,例如通过值传递ACRef并不具有典型的含义。但是只要你只允许通过ACRef读访问,这应该不是一个大问题。
如果代理类型最终出现在与原始对象不同的线程中,那么您必须非常小心。

我看不出你的方法有什么明显的错误,但明智的人告诉我要避免隐式类型转换,所以这让我有点紧张。每次利用"is-a"关系时,都有创建新对象的味道。

表示"is-a"关系的更惯用的方法是使用继承。

为了满足您对ACRef的需求,我们可以使用Base-from-Member习语:

struct D { int member; };
class ACRef {
  const D *data_ref;
 public:
  ACRef(const D &data) : data_ref(&data){}
  int getMember() const { return data_ref->member; }   
};
struct BaseA {
  D data;
  BaseA() : data({0}){}
};
class A1 : protected BaseA, public ACRef {
 public:
  A1() : ACRef(data){}
};
class A2 : protected BaseA, public ACRef {
 public:
  A2() : ACRef(data){}    
};
void printMyMember(const ACRef& a) {
  std::cout << a.getMember() << "n";
}
int main() {
  A1 a1;
  printMyMember(a1);
  D d = {1};
  ACRef acref(d);
  printMyMember(d);
}

我不认为你可以依赖offsetof(m_data)是一致的(你的例子混合了完整成员和指针)

为了得到保证的行为,你需要一个基类来定义通用的行为。

class Abase{ protected D* m_data;...}

只有这样,对于某些A

,从A*到Abase*的强制转换才是安全的(想象虚参被添加以支持析构函数,rtti)。