Мне кажется, что абстракции вокруг ваших типов Connection
усложняют ситуацию (тогда как абстракция должна упростить вещи).
Почему типы Connection
есть разные участники?Если производные классы Connection
вместо этого переопределяют BaseConnection
, вы можете положиться на диспетчеризацию виртуальных функций, чтобы делать правильные вещи во время выполнения.Например,
struct BaseConnection {
virtual void connect() {
cout << "BaseConnection::connect" << endl;
}
};
struct BetterConnection : public BaseConnection {
void connect() override {
cout << "BetterConnection::connect" << endl;
}
};
struct BestConnection : public BetterConnection {
void connect() override {
cout << "BestConnection::connect" << endl;
}
};
class X {
public:
X(std::unique_ptr<BaseConnection> connection)
: connection_(std::move(connection))
{
connection_->connect();
}
private:
std::unique_ptr<BaseConnection> connection_;
};
int main() {
X(std::make_unique<BaseConnection>());
X(std::make_unique<BetterConnection>());
X(std::make_unique<BestConnection>());
}
Если типы Connection
имеют разные методы, потому что они действительно выполняют разные действия, то возникает вопрос, является ли наследование подходящей абстракцией.
Возможно, выМожно добавить виртуальный метод, который вы переопределите, чтобы «делать правильные вещи» для каждого производного Connection
.Тогда классу более высокого уровня нужно вызвать только этот метод, и это можно сделать без приведения.
В общем, если вам нужно использовать dynamic_cast
для проверки типов во время выполнения, возможно, этоозначает, что интерфейсы не предназначены для полиморфизма.Я бы переосмыслил интерфейсы между вашими объектами и попытался бы выяснить, есть ли способ получить то, что вы хотите, без необходимости использовать upcast.
Редактировать: Использование черт типа
На основе вашихкомментарии, кажется, что вам может потребоваться больше настройки объектов более высокого уровня, чем мой первоначальный ответ.По сути, я думаю, что вы пытаетесь сделать это на основе того, каким базовым типом Connection
вы управляете, и предоставить различные реализации функций более высокого уровня.
Распространенный способ сделать это (и какSTL делает это) через перегрузку оператора чертами типа.Чтобы проиллюстрировать это, начните с нескольких типов, которые описывают черты базовых объектов соединения.
struct base_connection_tag {};
struct better_connection_tag : public base_connection_tag {};
struct best_connection_tag : public better_connection_tag {};
И затем мы можем добавить их к классам Connection
.
struct BaseConnection {
virtual void a() {
cout << "BaseConnection::a()" << endl;
}
using connection_category = base_connection_tag;
};
struct BetterConnection : public BaseConnection {
virtual void b() {
cout << "BetterConnection::b()" << endl;
}
using connection_category = better_connection_tag;
};
struct BestConnection : public BetterConnection {
virtual void c() {
cout << "BestConnection::c" << endl;
}
using connection_category = best_connection_tag;
};
По соглашению connection_traits
повторяет вложенный typedef класса Connection
template <typename ConnectionT>
struct connection_traits {
using connection_category = typename ConnectionT::connection_category;
};
Наконец, мы можем использовать перегрузку операторов, чтобы решить, какую реализацию вызывать в некотором классе (или классах) более высокого уровня, используяшаблон, подобный следующему:
template <typename T>
class Dispatch
{
public:
Dispatch(std::unique_ptr<T> connection)
: connection_(std::move(connection))
{}
void operator()() {
connect(typename connection_traits<T>::connection_category());
}
private:
void connect(base_connection_tag) {
connection_->a();
}
void connect(better_connection_tag) {
connection_->b();
}
void connect(best_connection_tag) {
connection_->c();
}
std::unique_ptr<T> connection_;
};
Когда вызывается оператор ()
, класс Dispatch
вызывает один из методов connect
, используя connection_traits
базового Connection
.
Поскольку все типы известны во время компиляции, класс Dispatch
знает, какой базовый метод вызывать во время компиляции.dynamic_cast
не требуется для определения того, какой тип удерживается.
Хотя я использовал только один шаблонный класс для реализации функциональности более высокого порядка, вы могли бы реально использовать несколько не шаблонных классов для того же, используя connection_traits
и перегрузка аргументов функции в каждом для включения / выключения функции.