LINUX.ORG.RU

Концепция передачи параметров в виде строки

 , , , ,


2

4

Есть проект (не важно, на каком языке, но пусть будет C++), в котором есть две так скажем мало связанные между собой части (функции/класса). И одна функция (класс) вызывает другую с передачей туда параметров. И сейчас в качестве интерфейса передачи параметров используется обычная строка, т.е. вызывающая функция генерирует строку вида «Имя_параметра1=значение_параметра1\nИмя_параметра2=значение_параметра2...», передаёт её вызываемой, где происходит парсинг параметров.

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

В общем, хочу обсудить: насколько это хорошо/плохо/универсально/неуниверсально? Можно ли так рекомендовать делать, если сам процесс вызова в программе происходит один раз (т.е. парсинг вообще не влияет на скорость выполнения)? Какие другие способы можете предложить (вызов разных функций через один интерфейс)?

Дискасс.

Update:

В качестве примера можно привести программу, обрабатывающую скрипт вида:

Вызвать Функцию1, параметры: п1=з1, п2=з2
Вызвать Функцию2, параметры: п3=з3, п4=з4

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

call_function(Функция1, "строка с параметрами")

★★★★☆

Последнее исправление: Sahas (всего исправлений: 1)

Ответ на: комментарий от Iron_Bug

что-то я тебя плохо понимаю... Никаких циклов в скриптах нет, только, по сути, список вызываемых функций с параметрами (это утрированный пример, хорошо отражающий суть задачи)

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

всё равно если есть вложенные вызовы - то стек, однозначно.

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

условно есть программа, которая считывает скриптовый файл и вызывает написанные в этом файле функции с передачей им параметров, которые считываются из этого же файла. Реализация этих функций сделано в виде виртуального класса, где виртуальная функция «call_function» принимает, условно говоря, одну строку со списком параметров, которую парсит внутри себя конкретная реализация этой функции.

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

Kroz ★★★★★
()

map<string, string> в интерфейсе.

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

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

И еще. Если это правда и есть задача - опять же не мучься, а возьми Lua - язык, который был специально создан для того, чтобы встраиваться в другое ПО.

Первый пример в этой статье - как получить переменную из lua скрипта.
Первый пример в этой статье - как из lua скрипта вызвать функцию C++.

Не сэкономит ли тебе это десяток-другой часов?

Kroz ★★★★★
()

Мы у себя решили это проще. Написали тип и пачку функций к нему, полностью аналогичный перловому storable. Т.е. нода может быть 4х типов: строка, инт, хэш или массив. И в функцию передаем ноду. Какие внутри там в ней ключи - волнует только адресата, а не интерфейс.

PPP328 ★★★★★
()

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

Вообще-то это непонимание основных принципов построения API. У тебя есть интерфейс, он фиксирует набор параметров, не может наследник требовать другого набора. Если требует, значит он к этому интерфейсу никакого отношения не имеет, и ты его зачем-то притянул к нему за уши. Либо твои параметры не являются частью интерфейса, тогда зачем ты вообще упомянул виртуальные функции?

В таком случае для передачи произвольного формируемого в рантайме набора ключ-значение используют всё-таки (unordered) map, а не в строки их сериализуют велосипедным и наверняка неэффективным и небезопасным способом.

Вызвать Функцию1, параметры: п1=з1, п2=з2
Вызвать Функцию2, параметры: п3=з3, п4=з4

Ты не смог внятно описать задачу, так что адекватных ответов можешь не ждать, но если ванговать то выглядит так, что тебе нужны нормальные невиртуальные методы Функция1(п1, п2) и Функция2(п3, п4) которые нужно звать напрямую, безо всякого там полиморфизма в конкретно этом месте.

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

В качестве альтернативы можно взять другой язык для твоего менеджера вызовов

Вы так написали, что можно подумать, будто C++ — недоразвитый ЯП, на котором задачу ТС решить нельзя и нужно обязательно мигрировать туда, где её решить легко.

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

Не сэкономит ли тебе это десяток-другой часов?

никак нет. И это только (малая) часть задачи.

Sahas ★★★★☆
() автор топика
Ответ на: комментарий от PPP328

Мы у себя решили это проще. Написали тип и пачку функций к нему, полностью аналогичный перловому storable. Т.е. нода может быть 4х типов: строка, инт, хэш или массив. И в функцию передаем ноду. Какие внутри там в ней ключи - волнует только адресата, а не интерфейс.

Не сказал бы, что это проще, но подход хороший

Sahas ★★★★☆
() автор топика
Ответ на: комментарий от Virtuos86

А почему бы не передавать туда массив с параметрами вместо строки?

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

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

У тебя есть интерфейс, он фиксирует набор параметров, не может наследник требовать другого набора.

В нашем случае может.

Ты не смог внятно описать задачу, так что адекватных ответов можешь не ждать, но если ванговать то выглядит так, что тебе нужны нормальные невиртуальные методы Функция1(п1, п2) и Функция2(п3, п4) которые нужно звать напрямую, безо всякого там полиморфизма в конкретно этом месте.

Возможно, тут полиморфизм не нужен.

Задача у меня вот эта: трассировка лучей

При пересечении луча с какой-либо поверхностью происходит вызов функции для этой поверхности, которая возвращает новые параметры луча. Функции для различных поверхностей могут быть разными, и то, какая функция должна вызываться для какой поверхности, описывается во входном файле (вместе с параметрами для этой функции).

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

А почему бы не передавать туда массив с параметрами вместо строки?

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

Обычный вектор. О какой идее идёт речь? У функций могут быть два вида аргументов: позиционные и именованные. Для первых достаточно вектора, для вторых — вектора кортежей (параметр, значение). Зачем строку-то парсить?

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

В качестве альтернативы можно взять другой язык для твоего менеджера вызовов

Вы так написали, что можно подумать, будто C++ — недоразвитый ЯП, на котором задачу ТС решить нельзя и нужно обязательно мигрировать туда, где её решить легко.

C++ недоразвитый в смысле отсутствия рефлексии. Или как вы предлагаете вызвать функцию по её имени в рантайме? Напомню, что ТС привел пример задачи

В качестве примера можно привести программу, обрабатывающую скрипт вида:

Вызвать Функцию1, параметры: п1=з1, п2=з2
Вызвать Функцию2, параметры: п3=з3, п4=з4
Crocodoom ★★★★★
()
Ответ на: комментарий от Crocodoom

Я исхожу из

Есть проект (не важно, на каком языке, но пусть будет C++), в котором есть две так скажем мало связанные между собой части (функции/класса). И одна функция (класс) вызывает другую с передачей туда параметров. И сейчас в качестве интерфейса передачи параметров используется обычная строка, т.е. вызывающая функция генерирует строку вида «Имя_параметра1=значение_параметра1\nИмя_параметра2=значение_параметра2...», передаёт её вызываемой, где происходит парсинг параметров.

То, что ТС привел как пример в Update выглядит как другая задача. А из постановки исходной задачи следует, что строкой представлены только параметры, а вызываемая функция становится известной каким-то другим образом, полагаю путем передачи указателя на неё. Потребности в рефлексии не вижу.

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

Чуть выше ТС наконец-то рассказал о своей задаче.

Задача у меня вот эта: трассировка лучей При пересечении луча с какой-либо поверхностью происходит вызов функции для этой поверхности, которая возвращает новые параметры луча. Функции для различных поверхностей могут быть разными, и то, какая функция должна вызываться для какой поверхности, описывается во входном файле (вместе с параметрами для этой функции).

Так что отображать имена функций в сами функции всё равно придётся. Мой скромный опыт говорит о том, что парсить этот входной файл удобнее на Python (или Lua, как предложили выше), и дергать C++ функции из разделяемой библиотеки.

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

Мой скромный опыт говорит о том

что, например, Rust прекрасно справится с такой задачей. В отличие от C++ в нём как минимум есть нормальные строки и паттерн матчинг. А ТС может делать на чём ему удобнее. Чтобы подружить питон с либой на плюсах, придется либо разбираться в написании нативных модулей для питона, либо дергать либу через ctypes. Не сказал бы, чтобы это выглядело удобнее, чем использовать один ЯП.

Virtuos86 ★★★★★
()

Разные функции переопределять в разных наследующих классах, которые истанцировать фабрикой по имени. Аргументом передавать в виде класса - хранилища аргументов, а каждая имплементация будет оттуда забирать какие ей надо аргументы по имени.

anonymous
()
Ответ на: комментарий от Virtuos86

Кто о чём, а вы о расте :) Если уж делать связку из двух языков C++ & something для скриптухи, то скриптовый ЯП в качестве something любом случае будет уместнее. Python, Lua, etc. Или вы предлагаете ТС делать проект целиком на Rust?

А кстати, в Rust есть рефлексия? (Я не знаю, поэтому спрашиваю). Как будет выглядеть код на Rust для разбора такого скрипта?

Вызвать Функцию1, параметры: п1=з1, п2=з2
Вызвать Функцию2, параметры: п3=з3, п4=з4

Придётся матчить имена вручную, или есть способы лучше?

Чтобы подружить питон с либой на плюсах, придется либо разбираться в написании нативных модулей для питона, либо дергать либу через ctypes. Не сказал бы, чтобы это выглядело удобнее, чем использовать один ЯП

Биндинги генерируются автоматически, руками ничего делать не придётся. Единственное, что придётся освоить новую технологию — не все хотят или могут это делать. И определенные временные затраты на освоение, конечно, надо заложить. Но если она уже освоена, как у ТС:

Если такой вариант приемлем, то могу набросать минимальный рабочий пример на Python & C++.

да не, спасибо, такую штуку я и сам использую в другом проекте.

То это именно намного удобнее, чем парсить такой скрипт на чистом C++. Знаю по себе.

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

Мой скромный опыт говорит о том, что парсить этот входной файл удобнее на Python (или Lua, как предложили выше), и дергать C++ функции из разделяемой библиотеки.

на современном C++ парсить простой текстовый файл тоже не большая проблема. Мне тут накидали много хороших вариантов, остался один не до конца прояснённый (для меня) общий вопрос: если во входном файле с данными разные части этих данных предназначены для разных «сущностей» в программе, нужно ли «убиваться» и делать один парсер для всего в начале, или можно всё-таки разные части перекидывать разным парсерам? Преимущества: 1) логическое разделение «кесарю — кесарево...»; 2) можно не особо париться об «унификации» синтаксиса входного файла. Недостатки: 1) дублирование кода парсера в разных частях программы; 2) гоняем строки «туда-сюда» вместо бинарных данных, ... (что-то ещё?)

Sahas ★★★★☆
() автор топика
Ответ на: комментарий от anonymous

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

с хранением всех аргументов в одном контейнере сразу возникает проблема возможной коллизии между совпадающими именами параметров для разных функций. Обходится легко, конечно, но чем тогда это лучше отдельного «унифицированного» хранилища для каждой функции?..

Sahas ★★★★☆
() автор топика
Ответ на: комментарий от Crocodoom

проблема дёргать функцию по её имени

К счастью, набор функций всё-таки ограничен (а главное, новые не могут в рантайме появляться), так что решается банальным ассоциативным массивом :)

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

А этот набор функций при дальнейшей разработке будет меняться? Если нет, или очень редко, то действительно можно обойтись ассоциативным массивом, или даже обычной лесенкой if -- else if -- else if ...

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

У функций могут быть два вида аргументов: позиционные и именованные. Для первых достаточно вектора, для вторых — вектора кортежей (параметр, значение). Зачем строку-то парсить?

Да, идея хорошая, возьму на вооружение...

С другой стороны, если парсить на уровне вызываемой функции, то проблема отсутствия нужного/наличия избыточного параметра решается «на месте». Понятно, что это не так сложно реализовать и в случае парсинга параметров в вызывающей функции... В общем, см. Концепция передачи параметров в виде строки (комментарий)

Sahas ★★★★☆
() автор топика
Ответ на: комментарий от Crocodoom

А этот набор функций при дальнейшей разработке будет меняться?

Меняться — вряд ли, скорее расширяться (да и то потихоньку)

или даже обычной лесенкой if — else if — else if ...

в общем-то, сейчас так и сделано :)

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

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

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

А кстати, в Rust есть рефлексия? (Я не знаю, поэтому спрашиваю). Как будет выглядеть код на Rust для разбора такого скрипта?

Вызвать Функцию1, параметры: п1=з1, п2=з2
Вызвать Функцию2, параметры: п3=з3, п4=з4

Придётся матчить имена вручную, или есть способы лучше?

Будет выглядеть как и на Питоне примерно:

use std::str::FromStr;
use std::error::Error;
use std::fmt;

//------------------------ Definition of a custom error ------------------------

#[derive(Debug, Copy, Clone)]
struct ParsingError;

impl fmt::Display for ParsingError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "ParsingError!")
    }
}

impl Error for ParsingError {
    fn description(&self) -> &str {
        "A sort of error."
    }

    fn cause(&self) -> Option<&Error> {
        Some(&*self)
    }
}

//------------------------------------------------------------------------------

#[derive(Debug)]
struct SerializedFunc {
    ident: String,
    args: Vec<String>,
}

impl FromStr for SerializedFunc {
    type Err = ParsingError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let arr: Vec<&str> = s.splitn(2, ", ").collect();
        let ident: String = arr[0].rsplit(' ').next().unwrap().to_owned();
        let args: Vec<String> = arr[1]
            .rsplit(": ")
            .next()
            .unwrap()
            .split(", ")
            .map(|s| s.to_owned())
            .collect();

        Ok(SerializedFunc { ident, args })
    }
}

//------------------------------------------------------------------------------

fn main() {
    let source = "Вызвать Функцию1, параметры: п1=з1, п2=з2
                  Вызвать Функцию2, параметры: п3=з3, п4=з4";
    for cmd in source.split('\n') {
        println!("{:?}", cmd.parse::<SerializedFunc>().unwrap());
    }
}
https://play.rust-lang.org/?gist=82a82f265e409e34c8b4f8ad49b0af78&version...

Но честно признаюсь, что смухлевал: использовал String вместо &str, потому как компилятор крепко озадачил меня лайфтаймами.

Придётся матчить имена вручную, или есть способы лучше?

Как в питоне, globals()[<ident>](<args>) сделать не получится, да.

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

В нашем случае может.

Ты не понял. То что я сказал - факт, тебе не нужно с этим спорить, тебе нужно это осознать чтобы не иметь в будущем проблем с архитектурой.

Задача у меня вот эта: трассировка лучей

Ты действительно думаешь что я не знаю что такое трассировка лучей?

При пересечении луча с какой-либо поверхностью происходит вызов функции для этой поверхности, которая возвращает новые параметры луча. Функции для различных поверхностей могут быть разными, и то, какая функция должна вызываться для какой поверхности, описывается во входном файле (вместе с параметрами для этой функции).

Я всё ещё не вижу всей задачи, но как я это представляю по предоставленным тобой скудным сведениям: у тебя полиморфные классы поверхностей. При загрузке файла ты парсишь из него параметры поверхности (индивидуальные для каждого типа поверхности), инстанцируешь сабкласс соответствующий этому типу поверхности и инициализируешь его опять таки индивидуальным образом через конструктор или обычные методы (свои для каждого сабкласса), как тебе удобнее. Потом все поверхности ты сохранешь в контейнер (как указатели на базовый класс), и дальше уже работаешь с ними единообразно через один интерфейс. В частности, для обработки падающего луча у тебя будет виртуальный метод Ray ProcessRay(const Ray&), который получает падающий луч и возвращает новый луч после взаимодействия с поверхностью.

Никаких сериализаций строк и передачи словарей не нужно ни на каком шаге. И вообще забудь о них если пишешь рейтрейсер.

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

ок, спасибо за разъяснения

В целом, так и делаем :)

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

Тут даже двойная диспатчеризация не нужна, обычные методы и обычные вызовы.

aedeph_ ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.