LINUX.ORG.RU

В чём суть ООП?


3

5

С точки зрения манеры проектирования, а не особенностей реализации С++ там CLOS. Вот например я использую определения классов и даже иногда наследование как узнать пишу ли я в ООП стиле?

★★★★★

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

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

А т.е. все типы изначально делаются максимально удобными для костылестроения.

это называется «вторичное использование существующего кода». Кстати, в OpenSource это не настолько важно, а в non-OpenSource часто просто нет другого выхода - определение класса и код для работы с ним у тебя будет в виде какой-нить закрытой DLL'ки.

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

ИМХО неправильно юзать cout << x

В STL правильнее чем printf() :)

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

Похожая ситуация с регэспами

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

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

А на variadic templates'ах еще можно fprintf подобную обертку сделать, которая все аргументы будет на этапе компиляции проверять.

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

для какого-нибудь своего подобного DSL, компилятор ничем не поможет,

Естественно.

и выхода для С++: либо делать интерфейс на перегруженных операторах как в Boost.Format, либо на шаблонах с произвольным числом параметров

Либо помахать ручкой попыткам вычислить все выражения в статике.

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

Либо помахать ручкой попыткам вычислить все выражения в статике.

Не вычислить всё в статике, а только обеспечить статическую проверку типов.

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

неправильно юзать cout << x

А что в этом плохого? Это же фактически send_message_to(stdout, show, x) или stdout.show(x), где stdout - объект, show(x) - сообщение, а send_message_to и точка могут работать элементарно и статично в духе си (show - имя поля), либо более сложно и динамически (если, например, таблица методов класса может изменяться в рантайме).

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

Чуть более чем все широко используемые компиляторы проверяют передаваемые параметры на соответствие формату.

Это по сути отдельный статический анализатор приделанный к группе функций. Если сделать свою variadic функцию/шаблон через vprintf то уже ничего не помешает сделать printf_(«%s\n», 123). Вот << / >> отличаются тем что их статические проверки это обычные типовые проверки.

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

Погромисты С в треде, все в машину. В машину? OH SHI~

Чудило, возьми и сравни первый твой вариант со вторым и осиль прочитать вот это

Сишный подход не идеален, но ООП-подобные варианты через '<<' чудовищны и неудобны

замена плюсовая костыльная и она не эквивалентна по юзабильности и удобности printf()'у.

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

Вот << / >> отличаются тем что их статические проверки это обычные типовые проверки.

Этим же они и ограничены. Если мы хотим в DSL иную систему типов, чем в плюсах, перегрузка плюсовых операторов нам ничем не поможет.

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

Обобщённый код - на то и обобщённый код, что он обобщённый. Напротив, конкретный код должен быть конкретным. К.О. Однако, пишем-то мы IRL конкретный код?

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

Шаблонная функция, приведённая мной, показывает пример утиной типизации как она есть. Как тут можно иметь своё имхо - не представляю; это всё равно что иметь своё имхо на результат 2+2.

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

Есть ещё один профит, хорошо проявляющийся в C++: код с RAII и другими идиомами хорошо проверяется статически. Забыть, скажем, разблокировать мьютекс просто невозможно - потому что вместо вызовов pthread_mutex_lock() и pthread_mutex_unlock() просто создаётся переменная Locker lock; - и с ней далее не происходит ничего. Память в С++ тоже можно разруливать автоматически - главное придерживаться строгих гайдлайнов и документировать передачу владения, вроде QAbstractItem переданного в QWidget.

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

Если мы хотим в DSL иную систему типов, чем в плюсах, перегрузка плюсовых операторов нам ничем не поможет.

В случае printf все его разумные применения это семейство безопасных функций которое вполне укладывается (известный тип * спецификатор * известный тип * спецификатор * ...) в систему типов С++ и как раз реализуется в iostreams в духе объектов / методов ООП (хотя и неудобно). Если бы были настоящие макросы, то можно было бы раскрыть вызов макроса printf в методы и послушать что компилятор про это думает (либо самим всё проверить при раскрытии используя любые нужные правила типизации).

А произвольный «DSL» my_eval(«``DSL`` lines here», ...) будет настолько хорош насколько будет хорош интерпретатор my_eval, но при этом всё равно не сможет ничего сказать в compile-time самого вызова my_eval. В таком случае (если DSL достаточно сложный и нужны проверки в compile-time) лучше сделать внешний язык *.dsl -> *.cc и интерпортабельно линковаться с этими *.сс -> *.o (как в protobuf, например). То есть всё сводится к тому что можно написать встроенный интерпретатор или внешний кодогенератор если нужен DSL сильно отличный от С++.

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

Шаблонная функция, приведённая мной, показывает пример утиной типизации как она есть. Как тут можно иметь своё имхо - не представляю;

а я не считаю такой шаблон утиной типизацией.

это всё равно что иметь своё имхо на результат 2+2.

2+2==11 в троичной системе исчисления.

2+2==2 если под символом '+' понимать дизъюнкцию (это общепринятая практика, кстати)

2+2==0 по модулю 2.

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

Есть ещё один профит, хорошо проявляющийся в C++: код с RAII и другими идиомами хорошо проверяется статически.

отмотай немного, и прочитай, почему я против утиной типизации: я потому против, что она приводит к трудноуловимым ошибкам рантайма. Очевидно твой вариант ни к чему такому привести не может, т.к. связывается во время компиляции.

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

В случае printf все его разумные применения это семейство безопасных функций которое вполне укладывается (...) в систему типов С++

Верно. Но решать эту задачу языковыми средствами С/С++ не удобно. А задача типовая.

А произвольный «DSL» my_eval(«``DSL`` lines here», ...) будет настолько хорош насколько будет хорош интерпретатор my_eval

Тоже верно.

В таком случае (если DSL достаточно сложный и нужны проверки в compile-time) лучше сделать внешний язык *.dsl -> *.cc

Опять же верно, именно так и делают - см. lex/yacc.

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

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

Кококо. Ты писал:

«cout << x» не разберётся как и что нужно выводить, в этом продходе просто нет эквивалентной замены форматированного вывода. Я прежде всего говорю о циферках a и b в «%a.b» и различных представлениях float чисел.

просто нет эквивалентной замены «%a.b» и различных представлениях float чисел.

Ну, я тебе и привёл эквивалентную по результату замену. Только она безопаснее и читабельнее. Да, букв больше при одинарном выводе (в хелловорлде). Если выводить нужно много чисел, то ты один раз настраиваешь точность и формат и пользуешься, а не пишешь в 20 местах как обезъяна одни и те же строки в printf.

Но в плюсах есть и другие интересные варианты. Помимо упоминавшегося здесь boost::format стоит взглянуть и на форматирования QString, которое довольно удобно.

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

именно так и делают

Генераторы лескеров и парсеров, описания данных protobuf, описания архитектур llvm. Больше подобного не помню.

Но форматная строка принтфа - промежуточный случай.

Ещё *scanf и pcre похоже работают (только в обратную сторону).

всех устраивает текущее решение, которое является компромиссом между удобством использования и «безопасностью».

Ну да, даже в хаскеле есть Text.Printf наряду с комбинаторными Text.PrettyPrint. Там printf безопасен в том смысле, что кидает исключения вместо сегфолтов. И format в CL который подобным образом работает. И интерполяции переменных из ruby. В общем, не лучший пример с iostream.

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

Больше подобного не помню.

Все компиляторы, которые используют другой язык (в т.ч. Си) как целевой код. Всевозможные xmlbeans, генераторы кода по wsdl и т.д.

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

Все компиляторы, которые используют другой язык (в т.ч. Си) как целевой код.

Да пусть даже в нативный код - если есть двустороннее FFI то можно добиться того же удобства (Lua, например, правда это уже не DSL). Но это уже совсем long-term вещи.

quasimoto ★★★★
()

Примерно так мне это видится:

  • Есть типы (классы или процессы) объекты которых имеют изменяемое состояние. То есть такие типы характеризуются не только своими данными, но и способами их изменения.
  • Для данного типа X может быть определён набор функций (методов или сообщений) I = {f, g, ...}, так, что на объекте типа X можно вызывать функции I (вызывать методы или посылать сообщения):
    f(x, additional_data)
    x.f(additional_data)
    X ! {f, additional_data}
    

    Такой набор сам по себе не зависит от X и представляет собой интерфейс который можно использовать для обращении к X и ко всем другим типам имеющим этот интерфейс (виртуальные функции, диспетчеризация, IPC с протоколом). При этом I - обобщённые функции по своей природе так как могут быть применены на объектах разных типов (интерфейс не связан с типом его реализующим), на самом деле обобщённый метод должен иметь реализации (это может быть реализация метода, поллинг процесса и т.п., то есть это не параметрическое поведение, но ad-hoc полиморфизм) для всех своих комбинаций типов, та или иная спецификация вибирается статически (рано) или динамически (поздно). Общение с объектом обычно происходит с помощью его интерфейсов без доступа к изменяющемуся внутреннему состоянию объекта (инкапсуляция, share-nothing).

  • Можно образовывать подтипы (наследование, агрегация, предки и потомки) A <: X, B <: X, ... так, что для всех них возможно безопасно реализовать интерфейс I (методы предка, override, пересылка сообщений) и говорить a.f(additional_data_2) и т.п. При этом вызов f на всех элементах некоего vector<X> должен привести к динамической (поздней) реакции разные реализаций (так как конкретные элементы могут иметь типы A, B и т.д.).
  • Множественная диспетчеризация, то есть не простая посылка «сообщение[опционально в самом сообщении: от кого и прочее] -> кому», а более сложная когда посылка сообщения f парам (a, b) и (b, a) может иметь разные эффекты. Но это реализуется и на основе примитивной диспетчеризации.
  • Что-то ещё?
quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Но это реализуется и на основе примитивной диспетчеризации.

Хотя с точки зрения распределённости множественная диспетчеризация f(A, B) для разных нод A и B это такое особое сообщение которое не только достигает A и B, но и заставляет их общаться. Например, любая вынужденная синхронизация это из этой серии.

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

Примерно так мне это видится:

Если мы о теоретическом ООП, то оно всегда базировалось на трёх китах:

1. Программа состоит из множества объектов, каждый из которых имеет свою идентичность.
2. Каждый из объектов принадлежит некоторому классу объектов, обладающих общими свойствами (в самом широком смысле этого слова).
3. Всё взаимодействие между объектами происходит путём передачи сигналов.

На практике такие системы реализованы в виде X Winodws System, WinAPI и Qt.

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

2. Каждый из объектов принадлежит некоторому классу объектов, обладающих общими свойствами

совершенно лишнее

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

Если выводить нужно много чисел, то ты один раз настраиваешь точность и формат и пользуешься, а не пишешь в 20 местах как обезъяна одни и те же строки в printf.

а ты не пиши как обезьяна

inline int myprintf(double x)
{
    return printf("%07.6", x);
}
drBatty ★★
()
Ответ на: комментарий от drBatty

Batty, да ты прям на лету облекаешь сложные абстракции в высокоуровневый код!

Правда, при этом:

1) теряются все преимущества DSL printf-а.

2) в проекте никакого преимущества у printf нет (так как всё равно одна строчка).

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

Это вариант с iostream, ничего принципиально не поменялось:

void bar(double value, int precision)
{
    using namespace std;
    cout << fixed << setw(8) << setprecision(precision) << value << endl ;
}

Покажи своё мастерство, владыка зависимостей, с использованием «некостыльного» printf.

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

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

как это соотносится с

Если выводить нужно много чисел, то ты один раз настраиваешь точность и формат и пользуешься, а не пишешь в 20 местах как обезъяна одни и те же строки в printf.

Ы?

Это вариант с iostream, ничего принципиально не поменялось:

int print_precesion(const char *prc, double x)
{
    return printf(prc, x);
}

но зачем?

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

Покажи своё мастерство, владыка зависимостей, с использованием «некостыльного» printf.

кстати, покажи-ка мне место, где я говорил о «некостыльности» printf(3) и вообще многоточия как аргумента в C? Не было такого. Конечно многоточие - это костыль. И ВСЕ функции с многоточием - суть костыли (printf -тоже).

Я говорил о то, что в iostream неправильно выбран объект, с т.з. Ъ ООП. Т.е. если мы хотим вывести некий X, мы должны отправить этому X некий message, и пусть он думает, как и куда себя выводить. А в iostream объектом выбран сам вывод (консоль, файл, или поток), что и приводит к костыльности. Это всё конечно ИМХО. Многие против, но мне по^W не важно.

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

Я говорил о то, что в iostream неправильно выбран объект,

Можно считать, что operator<<(std::ostream), T) является всего лишь обёрткой для T::write(std::ostream&), нужной лишь для того чтобы можно было удобно записать цепочку вывода: ostm << x << y, а не x.write(ostm); y.write(ostm).

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

Можно считать, что operator<<(std::ostream), T) является всего лишь обёрткой

она и есть обёртка, и ничего лучше тут ИМХО не придумать. Это никак не меняет того факта, что данная обёртка некошерна в смысле ООП.

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

Это никак не меняет того факта, что данная обёртка некошерна в смысле ООП.

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

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

сплошные утечки

ага. потому Джефф и советует делать деструкторы виртуальными. В этом случае, каким-бы образом ты не удалил объект, будут вызваны все нужные деструторы, причём в нужном порядке. Потратить на это лишние 4/8 байт - не настолько высокая цена (особенно учитывая, что в большинстве случаев и так придётся тратится на VTable).

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

Ну в смысле ООП (хотя только основанного на идее передачи сообщений, а не обощённых функций) некошерны все встроенные типы

истинно так.

но С++ - это не ОО язык.

когда речь идёт о низкоуровневом выводе (как в printf(3)), то ООП просто не нужно. О чём я и говорю уже третий день...

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

низкоуровневом выводе (как в printf(3))

1. Что такое низкоуровневый вывод?

2. printf - это обертка для fprintf(stdout, ...), а в fprintf, точнее в FILE ООП в полный рост.

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

Ох, спасибо, я и забыл, что в printf такое есть, но Batty, конечно это не спасло, он продолжает радовать.

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

Объект не должен решать _куда_ ему выводиться, только _как_. ostream - направление вывода operator<< - способ вывода. Что некошерного то?

son_of_a_gun
()
Ответ на: комментарий от aedeph_

s/каждый/большинство/

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

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