Как выкинуть этот объект без сращивания, основываясь на условии?

Я наткнулся на, вероятно, тривиальную проблему. Я хочу вызвать 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 , поэтому вам не понадобятся лямбды), но лямбды позволяют вам захватывать вещи, поэтому их не нужно слишком сильно перепроектировать.


Есть идеи?

10000