Допустим, у меня есть чистый абстрактный класс IHandler
и мой класс, который вытекает из этого:
class IHandler
{
public:
virtual int process_input(char input) = 0;
};
class MyEngine : protected IHandler
{
public:
virtual int process_input(char input) { /* implementation */ }
};
Я хочу наследовать этот класс в моем MyEngine, чтобы я мог передать MyEngine*
всем, кто ожидает IHandler*
и чтобы они могли использовать process_input
,
Однако я не хочу разрешать доступ через MyEngine*
так как я не хочу раскрывать детали реализации.
MyEngine* ptr = new MyEngine();
ptr->process_input('a'); //NOT POSSIBLE
static_cast<IHandler*>(ptr)->process_input('a'); //OK
IHandler* ptr2 = ptr; //OK
ptr2->process_input('a'); //OK
Можно ли это сделать с помощью защищенного наследования и неявного приведения?
Мне удалось получить только:
преобразование из ‘MyEngine *’ в ‘IHandler *’ существует, но недоступно
Поскольку я пришел из C # фона, это в основном явная реализация интерфейса в C #.
Это правильный подход в C ++?
Дополнительно:
Чтобы лучше понять, почему я хочу это сделать, подумайте о следующем:
Учебный класс TcpConnection
реализует связь по TCP, и в своем конструкторе ожидает указатель на интерфейс ITcpEventHandler
,
когда TcpConnection
получает некоторые данные на сокете, передает эти данные ITcpEventHandler
с помощью ITcpEventHandler::incomingData
или когда он запрашивает исходящие данные, которые он использует ITcpEventHandler::getOutgoingData
,
Мои занятия HttpClient
использования TcpConnection
(агрегация) и передает себя TcpConnection
конструктор и выполняет обработку в этих интерфейсных методах.
Так TcpConnection
должен реализовать эти методы, но я не хочу, чтобы пользователи использовали HttpClient
иметь прямой доступ к ITcpEventHandler
методы (incomingData
, getOutgoingData
). Они не должны быть в состоянии позвонить incomingData
или же getOutgoingData
непосредственно.
Надеюсь, это проясняет мой случай использования.
Получение с protected
делает члены базового класса недоступными через указатель на производный класс и запрещает неявное преобразование.
Мне кажется, что вам нужно не запретить доступ через базовый класс (интерфейс), а через производный класс (конкретную реализацию):
class IHandler
{
public:
virtual int process_input(char input) = 0; //pure virtual
virtual std::string name() { return "IHandler"; } //simple implementation
};
class MyEngine : public IHandler
// ^^^^^^
{
protected: // <== Make the functions inaccessible from a pointer
// or reference to `MyEngine`.
virtual int process_input(char input) { return 0; } //override pure virtual
using IHandler::name; //use IHandler version
};
Здесь, в производном классе вы в основном переопределяете видимость process_input
функция, так что клиенты могут вызывать их только через указатель или ссылку на базовый класс.
Таким образом, вы сделаете это невозможным:
MyEngine* ptr = new MyEngine();
ptr->process_input('a'); // ERROR!
std::cout << ptr->name(); // ERROR!
Но это будет возможно
IHandler* ptr = new MyEngine();
ptr->process_input('a'); // OK
std::cout << ptr->name(); // OK
В C ++ protected
а также private
наследство служит использованию наследование реализации. Это значит, что вы определяете класс с помощью методов, например, шаблонный класс, и когда вы хотите использовать его функциональность, но не интерфейс, вы наследуете protected
или же private
, Так что на самом деле ваш базовый класс должен определить методы, которые вы хотите использовать в подклассе.
Вот это ссылка на эту тему. Это действительно сложно, я согласен.
Немного трудно понять реальную цель, которую вы надеетесь достичь здесь, потому что независимо от того, вызываете ли вы метод у родителя или потомка, пока он виртуален, будет вызываться один и тот же.
Тем не менее, у вас есть несколько вариантов.
Вы можете сделать так, чтобы пользователь не мог получить указатель (или объект) дочернего типа путем принудительного вызова create, который возвращает интерфейс. Тогда вам не нужно беспокоиться об искусственных ограничениях, они просто не могут завести ребенка вообще:
class Concrete : public Interface
{
public:
static Interface* create() { return new Concrete; }
private:
Concrete() { }
};
Вы можете переопределить интерфейс как protected
как показано в другом ответе.
Вы можете использовать шаблон невиртуального интерфейса, чтобы сделать весь доступный общедоступный интерфейс, определенный в родительском. Тогда не имеет значения, какой у них объект, они всегда получают открытый API из класса интерфейса:
class Interface
{
public:
void foo() { foo_impl(); }
private:
virtual void foo_impl() = 0;
};
class Concrete
{
private:
virtual void foo_impl() { }
};