Simulating an interface with an implicit type

Suppose that I have a few classes A1

, A2

, A3

whose data can be stored in the same data structure of the POD D

. Classes differ in how data is manipulated, for example when dynamic data structures are allocated. However, semantically, they represent all the same data types.

One of my classes A?

is for wrapping around D

. It can be built on top of an existing one D

without copying it.

Now I want to unify read access for all classes A?

. I do not want to use virtual methods and do not want to templatize all the code where classes are used A?

.

Is the following a sane design?

Does this template have a name? (Facade?)

Are there any obvious pitfalls?

/* 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? */
int printMyMember (ACRef a) {
   std::cout << a.getMyMember () << std::endl;
}

A1 a1;
A2 a2;
// ...
printMyMember (a1);
printMyMember (a2);    

      

+3


source to share


3 answers


The only drawback I see with this approach is that you end up with multiple objects that are implicitly sharing state. So, for example, passing ACRef by value has no typical meaning. But as long as you allow read-only access through ACRef

, this shouldn't be a big problem.
If the proxy types end up on a different thread than the original object, you should be very carfull though.



+2


source


I don't see anything wrong with your approach, but wise people told me to avoid implicit type conversions, so I'm a little nervous. Something smells like creating a new object every time you use an "is-a" relationship.

A more idiomatic way of representing an "is-a" relationship would be to use inheritance.



To meet your requirements on ACRef

, we can use the Base-from-Member idioms:

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

      

0


source


I don't think you can rely on the consistency of offsetof (m_data) (your example mixes full member and pointers)

To get guaranteed behavior, you need a base class that defines the general behavior.

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

      

Only then is casting safe from A * to Abase * (imagine vtable being added to support destructor, rtti) for some A

0


source







All Articles