【问题标题】:Simulating an interface by implicit typecast通过隐式类型转换模拟接口
【发布时间】:2015-10-20 19:59:49
【问题描述】:

假设我有几个类A1A2A3,它们的数据可以存储在同一个POD数据结构D中。这些类在管理数据的方式上有所不同,例如,动态数据结构的分配位置。然而在语义上,它们代表所有相同类型的数据。

我的一个A? 类被设计成对D 的封装。它可以基于现有的D 构建而无需复制。

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

下面的设计合理吗?

这个模式有名字吗? (门面?)

有什么明显的陷阱吗?

/* 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);    

【问题讨论】:

  • 为什么 A* 结构没有 getMyMember() 方法,或者改为 getDStruct()?
  • 为什么不直接使用继承?
  • 乍一看应该没问题。你有什么特别的疑问?你能详细说明一下吗?或者你的问题是,如果这包含 Facade 模式?
  • 我的疑惑是由于经常阅读的建议不要使用隐式类型转换。第二个原因:我从未在任何地方看到过这种方法。
  • 如果我使用继承,A1、A2 和 A3 必须从 ACRef 继承。然而,要么它们都继承了 ACRef 的 D * const 数据成员,要么,如果我将 A? 的 D 数据成员放入 ACRef,那么我无法在不复制现有 D 结构的情况下构建 ACRef。

标签: c++ implicit-conversion facade


【解决方案1】:

我看不出你的方法有什么明显的错误,但聪明的人告诉我要避免隐式类型转换,所以这让我有点紧张。每次利用“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);
}

【讨论】:

    【解决方案2】:

    我看到这种方法的唯一缺点是您最终会得到多个隐式共享状态的对象。所以例如按值传递 ACRef 没有典型含义。但只要您只允许通过ACRef 进行读取访问,这应该不是什么大问题。
    如果代理类型最终位于与原始对象不同的线程中,那么您必须非常小心。

    【讨论】:

      【解决方案3】:

      我认为您不能依赖 offsetof(m_data) 保持一致(您的示例混合了完整成员和指针)

      为了获得保证的行为,您需要一个定义常见行为的基类。

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

      只有这样,对于某些 A,从 A* 到 Abase* 的转换才会安全(想象一下 vtable 被添加以支持析构函数,rtti)

      【讨论】:

      • 我不认为这是正确的,因为我没有(而且编译器不允许我)将A1* 转换为ACRef *,而是将A1 转换为ACRef
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-12
      • 2017-12-04
      • 2014-06-12
      • 2014-11-06
      相关资源
      最近更新 更多