Question

I'm new to C++, and am struggling with understanding data abstraction in combination with binary methods such as equality. I'd like to define an interface

class A {
 public:
  static A* Make(int);
  virtual ~A() {};
  virtual bool Eq(A*) = 0;
};

Using a factory pattern, I can hide the implementation:

class B : public A {
 public:
  B(int x) : x_(x) {}

  bool Eq(A* a) {
    return x_ == dynamic_cast<B*>(a)->x_;
  }

 private:
  int x_;
};

A* A::Make(int x) {
  return new B(x);
}

I can then use the abstraction:

A* a = A::Make(1);
A* b = A::Make(2);

if (a->Eq(b)) {
  cout << "Yes!" << endl;
} else {
  cout << "No!" << endl;
}

However, the dynamic cast sucks for a variety of reasons. The biggest problem as I see it is that one can subclass A with C, and then pass an object of type C to a->Eq, which would result in undefined behavior. I can not figure out how to define a binary method such that the code has access to the private members of both objects. My intuition is that this can be done using a visitor pattern, but I wasn't able to come up with a solution.

For those who know ML, I essentially want to do the following:

module A : sig
  type t
  val make: int -> t
  val eq: t -> t -> bool
end = struct
  type t = int
  let make x = x
  let eq x y = (x = y)
end
Was it helpful?

Solution 2

Expanding on my comment, you can write your code using the PIMPL (pointer to implementation) idiom as follows:

In your header file (a.hpp)

class A
{
public:
   A(int);
   ~A();
   bool Eq(const A& a) const;

private:
   class AImpl;
   AImpl* implementation_;
};

In the implementation file (a.cpp)

struct A::AImpl {
   AImpl(int x) : x_(x) {}
   int x_;
};

A::A(int x)
   : implementation_(new AImpl(x))
{}

A::~A()
{
   delete implementation_;
}

bool
A::Eq(const A& rhs) const
{
   return implementation_->x_ == rhs.implementation_->x_;
}

In your main file

#include "a.hpp"

int main()
{
   A one(1);
   A two(2);

   return one.Eq(two);
}

Because all of the actual code is in the implementation file, you cannot see it from other classes (which will only include the header file a.hpp). For clarity, I used a raw pointer, but in a real setting you would want to use an appropriate smart pointer.

OTHER TIPS

You don't want to make equality functions virtual.

Here's an example:

class Animal
{
  public:
    virtual bool is_equal(const Animal * p_a) = 0;
};

class Human : public Animal
{
  public:
    bool is_equal(const Animal * p_a);
};

class Toad : public Animal
{
  public:
    bool is_equal(const Animal * p_a);
};

If I create two variables of pointers to Animals:

   Animal * p_animal_human = new Human;
   Animal * p_animal_toad  = new Toad;

The virtual is_equal method allows me to compare two animal classes:

   if (p_animal_human->is_equal(p_animal_toad)

This can lead to a whole bunch of run-time errors and as this example, you don't want to compare that a Human is equal to a Toad. You may want to compare two Human instances or two Toad instances.

In the above if statement, only the Animal data, common to both classes, is compared.

Search StackOverflow for the term "[C++] slicing".

You may use something like:

class A {
public:
    virtual ~A() {};
    virtual bool Eq(const A& a) const = 0;
};

class B : public A {
public:
    explicit B(int x) : x_(x) {}

    virtual bool Eq(const A& a) const {
        const B* b = dynamic_cast<const B*>(&a);
        return b != NULL && Eq(*b);
    }
    bool Eq(const B& rhs) const { return x_ == rhs.x_; }

private:
    int x_;
};

class C : public A {
public:
    explicit C(int x) : x_(x) {}

    bool Eq(const A& a) const {
        const C* c = dynamic_cast<const C*>(&a);
        return c != NULL && Eq(*c);
    }
    bool Eq(const C& rhs) const { return x_ == rhs.x_; }

private:
    int x_;
};

int main() {
    B b1(42);
    B b2(42);
    C c(42);
    A& ab1 = b1;
    A& ab2 = b2;
    A& ac = c;

    std::cout << "ab1.Eq(ab2) = " << ab1.Eq(ab2) << std::endl; // true
    std::cout << "ab1.Eq(ac) = " << ab1.Eq(ac) << std::endl;   // false

    return 0;
}
  • "I can not figure out how to define a binary method such that the code has access to the private members of both objects"
  • In C++ you can access private member functions and protected member functions from another class or object types by using the keyword friend.
  • You need to include the class that contains the private member functions or protected member functions in your header file such as;
  • class A {
  • friend class B;
  • }
  • And you can also do it the other way round. Thus class B can be a friend of class A and class A can be a friend of class B.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top