LINUX.ORG.RU

Вызов функции из аргумента(C++)

 , ,


0

2

Пишу консольную программу «Органаизер» на С++ и дошел до момента проверки комманд. Пытаюсь сделать это через словарь(map)

map <string, void*> AvailableCommands;

То есть вводится команда, проверяется наличие ключа и после этого мне нужно запустить функцию, которая передаётся в качестве значения словаря. Как это сделать? Можно ли так делать или же лучше это сделать по другому?


Можно хранить указатель на функцию, можно std::function, можно вместо функций использовать классы, соответственно определить интерфейс твоих команд, а хранить инстансы классов унаследованных от него.

slovazap ★★★★★
()
Ответ на: комментарий от rupert

Только хардкор!

Нет там хардкора. Если Вы такой «добрый» - удачи объяснить товарищу как std::function устроена, что именно она делает и чего стОит по сравнению с вызовом через function pointer.

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Ответ на: комментарий от rupert

А как std::string устроена, видел?

Видел. Больше чем одну реализацию.

Пусть лучше char* гоняет.

Raw pointers - отменить (только смарты), function pointers - отменить (только лямбды), циклы с индексами - отменить (только итераторы). Итд. А потом имеем то что имеем - кучу Г которое едва ворочается и жрёт как не в себя.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

function pointers - отменить (только лямбды), циклы с индексами - отменить (только итераторы). Итд

так то и лямбды и итераторы быстрее чем function pointers и циклы с индексами.

В худшем случае одинаково будет, если компилятор правильно оптимизирует…

fsb4000 ★★★★★
()
Ответ на: комментарий от bugfixer

Если Вы такой «добрый» - удачи объяснить товарищу как std::function устроена, что именно она делает и чего стОит по сравнению с вызовом через function pointer.

А чего там обяснять - одинаково стоит что так, что так. std::function отличается только наличием каста перед вызовом, который на рантайм не влияет.

annulen ★★★★★
()
Ответ на: комментарий от bugfixer

Raw pointers - отменить (только смарты), function pointers - отменить (только лямбды), циклы с индексами - отменить (только итераторы). Итд. А потом имеем то что имеем - кучу Г которое едва ворочается и жрёт как не в себя.

Если писать код с головой на плечах, то оверхед от всего этого нулевой (или даже ускорение, если использовать итераторы с ассоциативными контейнерами вместо «наивного» индексирования)

annulen ★★★★★
()
Последнее исправление: annulen (всего исправлений: 1)
Ответ на: комментарий от bugfixer

Function pointers это сишное наследие с ублюдским синтаксисом. У std::function есть оверхед, но лучше начинать с него как более высокоуровневой и можной абстракции.

slovazap ★★★★★
()
Ответ на: комментарий от Jython

Только ошибка в TestFunc не пойму почему

Не вижу ни конкретных вопросов, ни текста ошибки - если такое повториться будешь отправлен читать книгу по C++ в одиночестве.

TestFunc должен быть свободной функцией или статическим методом. Если тебе нужно звать обычные методы, очевидно что для этого как минимум нужен инстанс класса при вызове.

slovazap ★★★★★
()
Ответ на: комментарий от rupert

А возможно ли всё это вызывать не в мэйне а в конструкторе? Сейчас я пытаюсь сделать так

//.h
class commandExecutor
{
private:
	map <string, std::function<void()>> AvailableCommands;
	string command;
public:
	commandExecutor();
	void TestFunc();
	
};

.cpp
commandExecutor::commandExecutor()
{
    AvailableCommands["Test"] = &commandExecutor::TestFunc;

}
Jython
() автор топика
Ответ на: комментарий от Jython

Поясню немного ответ @rupert. commandExecutor::TestFunc это метод класса commandExecutor. В любом нестатическом методе есть неявный аргумент this, указывающий на экземпляр класса, у которого вызывается этот метод.

Таким образом, для вызова команды этот this нужно как-то передать. &commandExecutor::TestFunc это указатель на метод класса, и this там нет. Одним из вариантов решения проблемы является использование замыканий, предложенное @rupert: замыкание сохраняет в себе this и вызывает функцию от него. Состояние замыкания будет скопировано в std::function, поэтому его можно будет вызывать без аргументов.

Альтернативным решением было бы вместо std::function использовать указатели на методы, что-то наподобие

std::unordered_map<std::string, int(commandExecutor::*)()> map;
map["test"] = &commandExecutor::TestFunc;

тогда для вызова this пришлось бы указывать явно

(*this.*map["test"])();

Синтаксис указателей на методы достаточно неочевидный, но их применение избавляет от необходимости хранить лишнее.

Siborgium ★★★★★
()
Ответ на: комментарий от rupert

Не, пусть поймёт и пользуется в продакте уже тем, что удобнее/необходимее по условиям. Динамические аллокации в std::function иногда сильно лишними становятся.

hatred ★★★
()
Ответ на: комментарий от annulen

одинаково стоит что так, что так.

https://godbolt.org/z/8xY3rc8nv

Это закрывает вопрос? Или стОит обсудить почему так происходит?

@fsb4000

так то и лямбды и итераторы быстрее чем function pointers и циклы с индексами.

В худшем случае одинаково будет, если компилятор правильно оптимизирует…

Наглая ложь. В лучшем случае - не хуже (когда весь статик контекст компилятору виден).

@Siborgium

Стоимость вызова действительно не отличается, но std::function значительно жирнее, так как SBO.

Приведённый тривиальный (спешу заметить) пример доказывает обратное.

bugfixer ★★★★★
()

вводится команда, проверяется наличие ключа и после этого мне нужно запустить функцию, которая передаётся в качестве значения словаря. Как это сделать?

Использовать какую-нибудь готовую библиотеку для разбора ключей командной строки…

Shushundr ★★★
()
Ответ на: комментарий от Jython

AvailableCommands[«Test»] = &commandExecutor::TestFunc;

Пожалуйста, не надо уродовать C++, не надо отдирать методы класса от класса! Вся сила C++ именно в ООП. В данном примере, как значение мапы, нужно хранить сам объект (или указатель на него).

raspopov
()

Я может непопулярную вещь скажу, но не надо запихивать обработку команд в словарь std::map. Простой if-else-if-else намного выразительней и гибче. На каждую команду ты сможешь вызвать какие-то обработчики, только с теми параметрами, что ему нужны. Или обработать команду-однострочник сразу на месте. Также ты можешь сделать обработку паттернов, например cmd.startsWith(prefix). Словарь же фиксирует и обобщает интерфейс обработчиков, заставляет выносить даже тривиальные обработчики в отдельную функцию или класс. Кроме того, словарь — это элемент рантайма и дополнительный источник энтропии. Что, если он мутабельный — держи в голове, откуда он модифицируется. Что, если команда записана в словаре, но ее обработчик не установлен (nullptr) — определяй семантику, ошибка это или эквивалент отсутствия команды в словаре. Что, если обработчик команды удалён — контролируй лайфтаймы (привет, ржавая мразь).

Хоть это и может показаться тривиальным, в перспективе развития проекта множеством людей (они приходят и уходят) на длительный период, чем больше такой динамики, тем больше и больше распухает контекст и информационный шум, с которыми приходится иметь дело программистам. Я не говорю, что динамика — плохо, но у неё должна быть лучшая причина чем «чтоб было красиво».

filosofia
()
Последнее исправление: filosofia (всего исправлений: 2)
Ответ на: комментарий от bugfixer

смарты лямбды итераторы

Что из этого являет собой «кучу Г которое едва ворочается и жрёт как не в себя»? Ну кроме лямбд, я слышал у них какой-то минимальный оверхед есть.

untitl3d
()
Последнее исправление: untitl3d (всего исправлений: 1)
Ответ на: комментарий от bugfixer

Приведённый тривиальный (спешу заметить) пример доказывает обратное.

вообще не доказывает.

Вы вообще видели тот ассемблерный вывод?

Вот что он делает если перевести его обратно на C++:

void CallMe1(FuncType1 func1)
{
   if (func1 == nullptr) {
       throw bad_function_call();
   }
   func1();
}

Очевидно что если писать корректный код нужно проверять самому на nullptr func1.

Так что это не минус, а плюс. Используя std::function совершаешь меньше багов.

А если компилятор знает что передали не nullptr, то он это проверку уберёт при inline, посмотрите ассемблерный код main:

https://godbolt.org/z/67GGsnP74

fsb4000 ★★★★★
()
Ответ на: комментарий от bugfixer

Если мы гарантируем компилятору что в CallMe2 не передадут nullptr, то он генерирует одинаковый код (насколько это возможно в силу разного размера указателя и std::function)

CallMe1(void (*)()):
        jmp     rdi
CallMe2(std::function<void ()>):
        jmp     [QWORD PTR [rdi+24]]
void CallMe1(FuncType1 func1)
{
   func1();
}

void CallMe2(FuncType2 func2)
{
    if(!func2) {
        __builtin_unreachable();
    }
   func2();
}

https://godbolt.org/z/8j4Pd7hnr

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 2)
Ответ на: комментарий от fsb4000

вообще не доказывает.

Вы вообще видели тот ассемблерный вывод?

Потрясающе, аплодирую стоя!

Вот только не надо мне рассказывать что один безусловный jump может быть медленнее чем то что случилось в случае std::function. Это ж какие яйца надо иметь чтобы даже при этом говорить «не не не, деды не понимают нифига, а у нас всё модно, молодёжно, и главное б.я - супер быстро»…

bugfixer ★★★★★
()

Благодарю за помощь всех. В итоге сейчас это выглядит так


commandExecutor::commandExecutor(){
    AvailableCommands["Test"] = std::bind(&commandExecutor::TestFunc, this);
   
    while (true){
        cout << "Enter command: ";
        getline(cin, command);
        
        if(commandIsCorrect(command)){
            AvailableCommands[command]();
        }
        else if (command == "exit"){
            break;
        }
        else
        {
            cout << "Command \""<<command << "\" doesn't exist. Type \"Help\" to get commands" << endl;
        }
    }


}

bool commandExecutor::commandIsCorrect(string com){
    if(AvailableCommands.find(command) != AvailableCommands.end()) { return true; }
    return false;
}

void commandExecutor::TestFunc(){
    cout << "Hello this is test func\n";
}

До этого я делал без разделения на заголовочные и cpp файлы и проверку комманд делал через switch, но мне показалось это немного «грязновато»

Jython
() автор топика
Ответ на: комментарий от Jython

Благодарю за помощь всех. В итоге сейчас это выглядит так

ты ипанулся что-ли? в конструкторе класса делать всю работу? конструктор КОНСТРУИРУЕТ класс и порождает обьект, который можно далее использовать.

не… ну можно и так как у тебя, плюсы все стерпят, то это противоречит и каноническим представлениям и здравому смыслу.

вообще желательно чтобы конструкторы никогда не генерили исключений и не имели сложной логики, тем более читали ввод пользователя.

alysnix ★★★
()
Ответ на: комментарий от bugfixer

Я про это уже больше 10 лет рассказываю, но они не понимают.

C++ программисты это диагноз, им хоть в глаза ссы.

Они вон, на компилятор надеются, с таким же успехом могли бы верить в магию или еще что

lovesan ★★
()
Ответ на: комментарий от alysnix

Исправил. Перенес всё это в main а в конструкторе оставил только добавление команд. Надеюсь я правильно понял замечание

commandExecutor::commandExecutor(){

    AvailableCommands["show date"] = std::bind(&commandExecutor::ShowDateCommand, this);

}

bool commandExecutor::commandIsCorrect(string com){
    if(AvailableCommands.find(com) != AvailableCommands.end()) { return true; }
    return false;
}

void commandExecutor::executeCommand(string com)
{
    AvailableCommands[com]();
}

void commandExecutor::ShowDateCommand()
{
    DateSetter currentDate;
    currentDate.showDate();
}
Jython
() автор топика
Ответ на: комментарий от lovesan

C++ программисты это диагноз, им хоть в глаза ссы.

На секундочку - именно таковым я и являюсь….

Они вон, на компилятор надеются

А вот это - не ко мне: пока на disasm не посмотришь - никогда не знаешь что именно происходит…

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 2)
Ответ на: комментарий от lovesan

Немного сарказма.

Как вот вообще на Си программы можно разрабатывать без использования стандартов, которые для C++ разработали?

В целом это не простой вопрос.
Лишний раз выскажу суждение о том, что сложность C++ не в замысловатом синтаксисе, а в том, что нужно помнить и понимать сотни «нельзя».

В целом C++ прекрасно подходит для системной разработки, а то что из него часто «уродца» делают, не его вина.

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 1)
Ответ на: комментарий от Jython
   if(AvailableCommands.find(com) != AvailableCommands.end()) { return true; }
    return false;

пиши короче

   return AvailableCommands.find(com) != AvailableCommands.end(); 

commandIsCorrect(string com){

тут передаешь строку по значению, а надо по констатной ссылке - типичная ошибка всяких там явщиков-питонистов

commandIsCorrect(const string &com){

не пиши длинных локальных имен. почитай coding guides.

alysnix ★★★
()