Я новичок в C ++ и пытаюсь понять абстракцию данных в сочетании с бинарными методами, такими как равенство. Я хотел бы определить интерфейс
class A {
public:
static A* Make(int);
virtual ~A() {};
virtual bool Eq(A*) = 0;
};
Используя фабричный шаблон, я могу скрыть реализацию:
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);
}
Затем я могу использовать абстракцию:
A* a = A::Make(1);
A* b = A::Make(2);
if (a->Eq(b)) {
cout << "Yes!" << endl;
} else {
cout << "No!" << endl;
}
Тем не менее, динамический бросок отстой по ряду причин. Самая большая проблема, как я вижу, состоит в том, что можно создать подкласс A с C, а затем передать объект типа C в a-> Eq, что приведет к неопределенному поведению. Я не могу понять, как определить двоичный метод так, чтобы код имел доступ к закрытым членам обоих объектов. Моя интуиция заключается в том, что это можно сделать с помощью шаблона посетителя, но я не смог найти решение.
Для тех, кто знает ML, я хочу сделать следующее:
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
Развернув мой комментарий, вы можете написать свой код, используя идиому PIMPL (указатель на реализацию) следующим образом:
В вашем заголовочном файле (a.hpp)
class A
{
public:
A(int);
~A();
bool Eq(const A& a) const;
private:
class AImpl;
AImpl* implementation_;
};
В файле реализации (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_;
}
В вашем основном файле
#include "a.hpp"
int main()
{
A one(1);
A two(2);
return one.Eq(two);
}
Поскольку весь реальный код находится в файле реализации, вы не сможете увидеть его из других классов (которые будут включать только заголовочный файл a.hpp). Для ясности я использовал необработанный указатель, но в реальной ситуации вы бы хотели использовать соответствующий умный указатель.
Вы не хотите делать функции равенства виртуальными.
Вот пример:
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);
};
Если я создам две переменные указателей на животных:
Animal * p_animal_human = new Human;
Animal * p_animal_toad = new Toad;
Виртуальный is_equal
Метод позволяет мне сравнить два класса животных:
if (p_animal_human->is_equal(p_animal_toad)
Это может привести к целой куче ошибок во время выполнения, и в качестве примера вы не хотите сравнивать Human
равно Toad
, Вы можете сравнить два Human
экземпляры или два Toad
экземпляров.
В выше if
заявление, только Animal
данные, общие для обоих классов, сравниваются.
Поиск StackOverflow по термину «нарезка [C ++]».
Вы можете использовать что-то вроде:
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;
}