Соответствие перегруженной функции ее полиморфному аргументу
Хорошо, название - глоток, и я думаю, что, вероятно, почему было трудно найти ответ через Google или этот сайт. Возможно, я не знаю, как правильно выражать проблему, но здесь говорится:
У меня есть ряд методов в классе SimpleOpenGLRenderer
, которые принимают один аргумент, расширяющий класс Model
. Таким образом, идея заключается в том, что в зависимости от типа модели рендерер будет вызывать правильный метод, который знает, как его отображать. Ниже приведен упрощенный пример исполняемого файла, основанный на проблеме:
#include <stdio.h>
class Model {};
class Cube : public Model {};
class Sphere : public Model {};
class Renderer
{
public:
virtual void renderModel(const Model& model) = 0;
};
class SimpleOpenGLRenderer
{
public:
void renderModel(const Cube& model)
{
printf("Render the cube.\n");
}
void renderModel(const Model& model)
{
printf("Throw an exception, my renderer does not support the model type you have provided.\n");
}
void renderModel(const Sphere& model)
{
printf("Render the sphere.\n");
}
};
int
main(int argc, char** argv)
{
Cube cube;
Model& model = cube;
SimpleOpenGLRenderer renderer;
renderer.renderModel(cube);
renderer.renderModel(model);
}
Результат из примера:
Render the cube.
Throw an exception, my renderer does not support the model type you have provided.
Для опытного разработчика на С++ может показаться очевидным, что это не работает так, как планировалось, но это просто не имеет смысла для меня. Во время выполнения я не буду знать точный тип Model
, переданный рендереру (следовательно, попытка перегрузки для его устранения). Исходя из фона Java, я использовал эту технику до и в Java, метод, который будет вызван, будет таким, который наилучшим образом соответствует типу времени выполнения аргумента. В С++ он, похоже, соответствует типу ссылки на компиляцию, даже если эта ссылка может оказаться в подклассе, который, на мой взгляд, лучше соответствует другой функции.
До сих пор я использовал этот тип времени выполнения как должное. Это просто не существует в С++, или я об этом неправильно? Должен ли я делать что-то по-другому на С++, чтобы достичь этого?
Спасибо,
Гэри.
Ответы
Ответ 1
Перегрузки в С++ разрешаются во время компиляции на основе статического типа аргумента.
Там может быть полезен метод, известный как "двойная отправка":
class Model {
virtual void dispatchRender(Renderer &r) const = 0;
};
class Cube : public Model {
virtual void dispatchRender(Renderer &r) const {
r.renderModel(*this); // type of "this" is const Cube*
};
int main() {
Cube cube;
Model &model = cube;
SimpleOpenGLRenderer renderer;
cube.dispatchRender(renderer);
}
Обратите внимание, что базовый класс Renderer
должен содержать все перегрузки, которые выполняет SimpleOpenGLRenderer
. Если вы хотите, чтобы это было специфично для SimpleOpenGLRenderer
, какие существуют перегрузки, вы могли бы поместить функцию "Особая конкретная отправка" в Model
или вы могли бы игнорировать эту технику и вместо этого использовать dynamic_cast
несколько раз в SimpleOpenGLRenderer::renderModel
для проверки типа.
Ответ 2
В вашем коде функции перегрузки разрешены на основе статического типа аргумента.
Вероятно, вам нужен механизм double-dispatch
, который очень близок к шаблону посетителя. Прочтите эти данные:
Ответ 3
Для "перегрузки во время выполнения" на основе динамического типа можно использовать шаблон посетителя.
Ответ 4
Ваш код является хорошим кандидатом на совпадение типов выполнения, если вы его используете. Здесь вы получаете Cube
в Model&
и передаете то же самое просто на renderModel()
. До сих пор у вас не было возможности компилятору использовать тип времени выполнения. Но скорее полагаясь на статический тип объекта.
Двумя способами вы могли бы использовать проверку типа времени выполнения. Один использует dynamic_cast<>
, а другой - метод интерфейса в Model
. то есть.
class Model {
virtual void print () { printf("throw..."); } // provide an interface method
};
class Cube : public Model {
virtual void print () { print("Render cube\n"; } // implement it
};
class Sphere : public Model {
virtual void print () { print("Render sphere\n"; } // implement it
};
class SimpleOpenGLRenderer
{
public:
void renderModel(const Model& model)
{
model.print();
}
};
Ответ 5
В С++ разрешающая перегрузка для вызова выполняется во время компиляции.
Чтобы эффективная реализация зависела от типа полиморфного аргумента, вам нужно проконсультироваться с этим аргументом, то есть вызвать виртуальный метод для аргумента.
Я думаю, что самый чистый способ сделать это - это то, что называется шаблон посетителя. Ваш SimpleOpenGLRenderer
может вызвать метод model.renderOn( *this )
. Тогда Model::renderOn
представляет собой либо набор перегрузок, по одному для каждого возможного типа средства визуализации, либо это единственный виртуальный метод, который использует dynamic_cast
для обнаружения типа средства визуализации. В любом случае он затем обращается к рендереру, но теперь этот вызов знает, какой тип рендерера он и какой тип сам, и может также вызвать очень конкретный метод рендеринга, например, SimpleOpenGLRenderer::renderCube
.
Приветствия,
Ответ 6
Другие решения здесь будут делать именно то, что вы хотите. Но, на мой взгляд, ценой сложности. Если ваша проблема будет точно такой, как описано, я бы предложил изменить архитектуру вашего решения. Разве рендер не пытается выполнить работу модели? То, что я вижу, является выражением предложения с перегрузкой с перегрузкой.
Как сделать модели рендеринга самостоятельно, возможно, используя некоторый класс, предлагающий более примитивные методы рисования:
class Cube : public Model {
render(RenderTool& tool) {
tool.renderCube(); //or even more low level
}
};
class SimpleOpenGLRenderer {
public:
RenderModel(Model& model) {
model.render(renderTool);
}
private:
SomeOpenGLRenderingTool renderTool;
};