Я наткнулся на, вероятно, тривиальную проблему. Я хочу вызвать doSomething()
для экземпляра AbstractParser
, где указанный экземпляр является производным классом FooParser
или BarParser
. Будет ли это один или другой, зависит от аргументов времени выполнения, предоставленных пользователем. Оставшаяся часть кода должна иметь только способ доступа к родительскому объекту AbstractParser
. См. ниже.
AbstractParser& myParser; // Error: can't have a reference to nothing
if(some_condition){
FooParser parser = FooParser();
myParser = parser;
}else{
BarParser parser = BarParser();
myParser = parser;
}
// lots of code with other things ...
parser.doSomething();
Исходя из условия, я хочу использовать myParser
(или указатель на него) вне области действия блока if
, а myParser
должен вести себя как соответствующий производный объект. Я хочу избежать копирования моего объекта и (если возможно) конструктора перемещения. Без условия я мог бы сделать это
FooParser parser = FooParser();
AbstractParser &myParser = parser; // upcast without slicing
// lots of code with other things ...
parser.doSomething();
И это было бы чисто и изящно, вычурно без нарезки parser
. Я не уверен, как это сделать сейчас.
Как я могу выгружать свои производные парсеры, не копируя созданные объекты и не выводя их из области видимости? Нужно ли создавать std::unique_ptr<AbstractClass>
и использовать make_unique<FooParser>()
внутри if
? Или я должен использовать функцию, которая возвращает ссылку на производный класс? Или static_cast<AbstractClass>
? Или я это слишком усложняю?
Всего 4 ответа
Простое решение - использовать unique_ptr
:
std::unique_ptr<AbstractParser> myParser;
if (some_condition)
myParser = std::make_unique<FooParser>();
else
myParser = std::make_unique<BarParser>();
myParser->doSomething();
Если вы хотите избежать выделения кучи, вы должны использовать что-то вроде std::variant
, но это не так удобно:
std::variant<std::monostate, FooParser, BarParser> var;
AbstractParser *myParser;
if (x)
myParser = &var.emplace<FooParser>();
else
myParser = &var.emplace<BarParser>();
myParser->doSomething();
Вы можете сделать следующее:
std::variant<std::monostate, FooParser, BarParser> parser;
if(some_condition) {
parser = FooParser();
} else {
parser = BarParser();
}
std::visit([](auto & p) {
if constexpr(std::is_same_v<std::decay_t(decltype(p)), std::monostate>) {
} else {
p.doSomething();
}
} ,parser);
Я думаю, что это может даже исключить динамическую диспетчеризацию для вызова функции.
Самый простой способ сделать это - извлечь код для разделения методов:
class Foo {
void subAction(AbstractParser& parser) {
parser.doSomething();
}
void action(bool some_condition) {
if (some_condition) {
FooParser foo;
subAction(foo);
} else {
BarParser bar;
subAction(bar);
}
}
};
Обратите внимание, что кучи не участвует.
Вы можете абстрагировать использование парсера от функции и затем вызвать его. Так из:
AbstractParser& myParser;
if (condition()) {
FooParser parser = FooParser();
stuff_1();
myParser = parser;
} else {
BarParser parser = BarParser();
stuff_2();
myParser = parser;
}
stuff_3(myParser);
Для того, чтобы:
([](auto&& use_parser){
if (condition()) {
FooParser parser = FooParser();
stuff_1();
use_parser(parser);
return;
} else {
BarParser parser = BarParser();
stuff_2();
use_parser(parser);
return;
}
})([](AbstractParser& myParser) {
stuff_3(myParser);
});
Может быть проще с совершенно отдельной функцией (например, в этом случае stuff_3
просто stuff_3
, поэтому вам не понадобятся лямбды), но лямбды позволяют вам захватывать вещи, поэтому их не нужно слишком сильно перепроектировать.