LINUX.ORG.RU

Может кто нибудь показать красоту с++?

 ,


7

9

Может кто нибудь написать на c++, чтобы показать почему c++ лучше смотрится чем программа на си? Хотелось бы увидеть изящный код на c++, так, как это делают с хорошим опытом. Программу любую, главное чтобы было понятно, что c++ намного красивее в написании, чем си.

Нет, конечно. </thread>

t184256 ☕☕☕☕☕
()

Надеюсь, речь идет не о синтаксической красоте, так как в этом плане С++ бывает очень уродлив, имхо. Если же говорить о красивых архитектурных решениях, то мне нравится stl. Попробуй написать что-либо на голых сях, а потом на С++11/14/17 с применением STL, контраст тебя поразит.

driver
()

Тебе ещё не рассказывали что такое namespace и RAII? Если нет, то стоит посмотреть.

anonymous
()

Синтаксически: что угодно на Qt, особенно, если шаблоны использовать. То, что на кьютах реализуется всего в несколько строчек, на сях займёт от сотен до нескольких тысяч строк чистого кода (разумеется, беру только QtCore). В сторону STL смотреть не советую, IMHO это худшее, что можно придумать с точки зрения эстетики и удобства.

Архитектурно: зависит от задач, притом это зависимость не столь от языка, сколь от стиля программирования. Что-то лучше решается процедурным стилем, что обычно делается на сях, что-то через ООП, где уже вотчина плюсов. Разумеется, есть исключения, как lm-sensors, написанный на плюсах в процедурном стиле, или linux, где внутри своё ООП нагородили

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

Попроси ещё пример грациозной Java.

Как язык жава вполне себе грациозен. А рукожопов их везде много, на любом языке хорошего кода мало в природе

redixin
()

Открой libcxx и glibc. В стандарте C++ описаны базовые примитивы, которых нет в C и нет RAII. Т.е. код на C получается более объемный и в случае отсутствия смартпоинтеров/классов более сложным для чтения. Я бы не сказал что у меня богаты опыт разработки на C/C++ и возможно люди с опытом в 20 лет могут писать безопасный код сразу, но у меня фаззеры и санитайзеры частенько находят какой-нибудь рейс или use after free.

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

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

xpahos
()

Лучший код, это тот который не написали.

Ну ты понел, почему этот камент плох. Бла бла бла, и всё такое.

pon4ik
()
#include <iostream>

int main(int argc, char** argv)
{
  std::cout <<
          "   cccc                                          \n"
         "  ccc              ++             ++              \n"
        " ccc               ++             ++                 \n"
        "cc             ++++++++++     ++++++++++              \n"
        "cc             ++++++++++     ++++++++++              \n"
        " ccc               ++             ++                 \n"
         "  ccc              ++             ++              \n"
          "   cccc"
  << std::endl;
  return 0;
}
aureliano15
()

Case 1: Маленький пример работы со строками

Например вот как можно конкатенировать строки - см. ниже.

Во-первых, более наглядно.

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

#include <iostream>
#include <string>

using namespace std;

int main()
{
        string s1, s2, s3;

        s1 = "The";
        s2 = "string";
        s3 = s1 + " " + s2;

        cout << "The result is: "<< s3 << endl;

        return 0;
}
$ g++ ./cpp_string.cpp && ./a.out
The result is: The string

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

Case 2: Перегрузка операторов

В case 1 была показана работа простыми операторами со сложным типом - строкой. На самом деле ты такое можешь конструировать для любого типа. Для стандартных типов это скорее вред, чем польза, а вот для своих кастомных типов может существенно облегчить чтение кода.

Внизу примеры чуть неправильные (я ж говорил, что лучше этого не делать для стандартных типов), но отражают суть.

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

Пример 1: «плюс» означает конкатенацию массивов:

#include <iostream>
#include <vector>

using namespace std;

vector<int> operator+(vector<int> &a, vector<int> &b)
{
        vector<int> r(a);

        r.insert(r.end(), b.begin(), b.end());

        return r;
}


int main()
{
        vector<int> v1 = {1, 2, 3};
        vector<int> v2 = {4, 5, 6};
        vector<int> v3;

        v3 = v1 + v2;

        cout << "Vector content: ";
        for (auto e: v3)
                cout << e << " ";
        cout << endl;
		
        return 0;
}
$ g++ ./cpp_vector_merge.cpp && ./a.out
Vector content: 1 2 3 4 5 6

Пример 2: «плюс» означает сумму элементов массивов:
#include <iostream>
#include <vector>

using namespace std;


vector<int> operator+(vector<int> &a, vector<int> &b)
{
        vector<int> r;
        auto i_b = b.begin();

        for (auto e_a: a)
                if (i_b != b.end())
                        r.push_back(e_a + *i_b);

        return r;
}


int main()
{
        vector<int> v1 = {1, 2, 3};
        vector<int> v2 = {4, 5, 6};
        vector<int> v3;

        v3 = v1 + v2;

        cout << "Vector content: ";
        for (auto e: v3)
                cout << e << " ";
        cout << endl;
		
        return 0;
}
$ g++ ./cpp_vector_add.cpp && ./a.out
Vector content: Vector content: 5 6 7

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

Case 3: RAII

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

И этот принцип может относиться к любым ресурсам, не только к памяти. Да и не только к ресурсам. В примере ниже еще и корректно закрывается файл.

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

И поскольку это подход заложен много где (в том числе в стандартной библиотеке шаблонов), в С++ практически никогда не нужно самостоятельно выделять и очищать память. В отличии от С, где это делается повсеместно.

#include <iostream>
#include <cstdio>

using namespace std;

class Container
{
public:
        int *mem;
        FILE *f;

        Container()
        {
                mem = new int [100];
                f = fopen("test.txt","r");
                cout << "Allocate" << endl;
        };
        ~Container()
        {
                delete [] mem;
                fclose(f);
                cout << "Release" << endl;
        };
};

void fn()
{
        Container a;

        cout << "Inside fn()" << endl;
};

int main()
{
        fn();

        return 0;
}
$ g++ ./cpp_RAII.cpp && ./a.out
Allocate
Inside fn()
Release

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

Case 4: Исключения

Одна из самых классных и при этом самых опасных фич С++ - исключения.

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

Чтобы предыдущей функции также завершиться аварийно в случае, если вызванная ею функция завершилась аварийно, там не нужно делать ничего (!). Выход из нее будет сделан автоматически, если только не поставить там блок try-catch. И при этом корректно будут вызваны все конструкторы локальных объектов, а значит и проблем с не-освобожденной памятью (да и другими «завершающими» операциями) нет.

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

#include<iostream>
#include<string>

using namespace std;

int fn1()
{
        // throw string("Exception in fn1");
        return 1;
}
int fn2()
{
        throw string("Exception in fn2");
        return 2;
}
int fn3()
{
        // throw string("Exception in fn3");
        return 4;
}

int fn()
{
        return fn1() + fn2() + fn3();
}

int main()
{
        string e;

        try {
                cout << "The resut is " << fn() << endl;
        } catch (string &s){
                cout << "Error: " << s << endl;
        }

        return 0;
}
$ g++ ./cpp_exception.cpp && ./a.out
Error: Exception in fn2

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

RAII сделает твою жизнь прекрасной. А исключения избавят от лапши из return error.

ox55ff 🤡🤡🤡🤡🤡
()
Ответ на: комментарий от SR_team

Вы тут в примере 2 забыли про итерацию второго вектора.

И правда.

Case 2, пример 2 (исправленный): «плюс» означает сумму элементов массивов:

#include <iostream>
#include <vector>

using namespace std;


vector<int> operator+(vector<int> &a, vector<int> &b)
{
        vector<int> r;
        auto i_b = b.begin();

        for (auto e_a: a)
                if (i_b != b.end()) {
                        r.push_back(e_a + *i_b);
                        i_b++;
                }

        return r;
}


int main()
{
        vector<int> v1 = {1, 2, 3};
        vector<int> v2 = {4, 5, 6};
        vector<int> v3;

        v3 = v1 + v2;

        cout << "Vector content: ";
        for (auto e: v3)
                cout << e << " ";
        cout << endl;
}

$ g++ ./cpp_vector_add.cpp && ./a.out
Vector content: 5 7 9

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

То, что на кьютах реализуется всего в несколько строчек, на сях займёт от сотен до нескольких тысяч строк чистого кода

Ну так покажи.

linuhs_user
()
Ответ на: Case 3: RAII от Kroz

в С++ практически никогда не нужно самостоятельно выделять и очищать память

То-то во всех учебниках по плюсам на каждый чих new используют. Даже тот же Шлее в учебнике по Qt.

meliafaro
()

А как будет выглядеть такой пример со строками в C++

fn reverse(input: &str) -> String {
    let mut output = String::with_capacity(input.len());
    for word in input.split_whitespace().rev() {
        if !output.is_empty() {
            output += " ";
        }
        output += word;
    }
    output
}

fn main() {
    let text = "my little pony, friendship is magic";
    let result = reverse(text);
    println!("{}", result);
}
Выводит:
magic is friendship pony, little my

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

энтерпрайз

Что такое энтерпрайз и энтерпрайз разработчики?

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

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

#include <string>
#include <sstream>
#include <iostream>

using std::string;

string reverse(string &str) {
    std::stringstream ss(str);
    string out = "";
    string line;
    while (std::getline(ss, line, ' ')) {
        if (!out.empty()) {
            out = " " + out;
        }
        out = line + out;
    }
    return out;
}

int main() {
    string text = "my little pony, friendship is magic";
    string result = reverse(text);
    std::cout << result << std::endl;
}
Если что, по поводу владения и кол-ва копирования в моём случае, я всё прекрасно понимаю :)

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

То-то во всех учебниках по плюсам на каждый чих new используют.

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

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

Еще бы все остальное было как в нормальных языках, но это же C++...

Можно список хотя бы из 5 пунктов по поводу «всего остального»?

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

Ну и, может быть, стандартную библиотеку побогаче. Хотя многие и STL-то освоить не могут. Так что тут все не так однозначно.

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

Угу, например с бустом. Выглядит почти 1 в 1.

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

Ого, я конечно не ТС, но за ссылку спасибо, я впечатлен!

Kronick
()

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

anonymous
()
#include <cstdio>

template <typename T>
struct Plus : T
{
  struct Params : T::Params {
    int to_add;
  };

  const int value;
  Plus (const Params &p) : T(p), value(p.to_add) {}

  void action(int &i) {i += value; T::action(i);}
};

template <typename T>
struct Minus : T
{
  struct Params : T::Params {
    int to_sub;
  };

  const int value;
  Minus (const Params &p) : T(p), value(p.to_sub) {}

  void action(int &i) {i -= value; T::action(i);}
};

struct iface {
  virtual ~iface() {}
  virtual void action(int &) = 0;
};

struct End {
  struct Params {};
  End(const Params &) {}
  void action(int &) {}
};

template <typename T>
struct Algo : iface
{
  T *t;

  Algo(const typename T::Params &p) : t(new T(p)) {}
  ~Algo() {delete t;}

  void action(int &i) override {
    t->action(i);
  }
};

struct CreateParams {
  int plus;
  int minus;
};


template <typename T>
iface *create_minus(const typename T::Params &p, const CreateParams &cp)
{
  if (cp.minus) {
    typename Minus <T> :: Params p0;
    (typename T::Params &)p0 = p;
    p0.to_sub = cp.minus;
    return new Algo <Minus <T> > (p0);
  } else {
    return new Algo <T> (p);
  }
}

template <typename T>
iface *create_plus(const typename T::Params &p, const CreateParams &cp)
{
  if (cp.plus) {
    typename Plus <T> :: Params p0;
    (typename T::Params &)p0 = p;
    p0.to_add = cp.plus;
    return create_minus <Plus <T> > (p0, cp);
  } else {
    return create_minus <T> (p, cp);
  }
}

int main() {
  CreateParams cp;
  cp.plus = 2;
  cp.minus = 1;
  iface *obj = create_plus <End> (End::Params(), cp);

  int i = 10;
  obj->action(i);

  printf("%d\n", i);
}

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

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

То, что на кьютах реализуется всего в несколько строчек, на сях займёт от сотен до нескольких тысяч строк чистого кода

Не надо сравнивать «тулкит/либа против си-голожопика», возьми тогда голожопые кресты в том числе без бустов и сравнивай ))

deep-purple
()
Ответ на: комментарий от XMs

То, что на кьютах реализуется всего в несколько строчек, на сях займёт от сотен до нескольких тысяч строк чистого кода

Красава, культи так хитрожопо приплел.

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

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

У всех, кто пишет на чистых сях, обычно есть библиотека потипу stl, написанная специально для проекта. Так что никакого контраста не будет.

Deleted
()
Ответ на: Case 3: RAII от Kroz

+

В твоих примерах — имплементация. Вот это все можно и на сях запилить, причем так, что будет работать и там и там, ваши же операторы в сях не взлетят. И я не вижу разницы (кроме чуть больше букв) «arrType *arr = arr_sum(arr1, arr2);» и «arrType *arr = arr1 + arr2;»

риска не-освободить память нет

Это как пошел посрать, но не посрал. Бывает конечно, но по форсмажорным причинам.

исключения

goto и long_jmp.

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

deep-purple
()
Ответ на: комментарий от XMs

То, что на кьютах реализуется всего в несколько строчек, на сях займёт от сотен до нескольких тысяч строк чистого кода (разумеется, беру только QtCore)

Зачем сравнивать высокоуровневую прикладную библиотеку со стандартным Си? QtCore уместнее сравнивать с glib, и разрыв в количестве строк сократиться до минимума, а в читабельности там вообще паритет.

meliafaro
()
Ответ на: комментарий от linuhs_user
QMainWindow win;
win.show();

Вот. А теперь на сях сделай тоже самое для иксов, для винды, для вяленого. Но я сказал уже выше что сравнивать голожопые си с тулкитом некорректно.

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

И зачем нужен весь этот ужас?

на С этот код превратится либо в тормознутое говно либо в проект размером в линукс кернел

В linux-kernel тебе скажут, что Си спасает их от персонажей, которым мощь шаблонов снесла крышу.

tailgunner
()
Ответ на: комментарий от deep-purple
SDL_Window* win = SDL_CreateWindow("Легко!",0,0,100,100,SDL_WINDOW_SHOWN);

Запустится даже на KOS.

linuhs_user
()
Последнее исправление: linuhs_user (всего исправлений: 1)
Ответ на: Case 4: Исключения от Kroz

Одна из самых классных и при этом самых опасных фич С++ - исключения.

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

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

Да взять тот же QMap (или std::map, чтоб сторонние библиотеки не тащить). В сях придётся писать по реализации на каждую пару типов. Этого уже достаточно

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

GHashMap? Можно еще так

#define IMPL_ARR(NAME, TYPE)\
  typedef struct {\
    TYPE* data;\
    int length;\
  } NAME;\
  void NAME ## New() {\
    NAME* arr = malloc(sizeof(NAME));\
    ...\ 
    return arr;\
  }

IMPL_ARR(ArrInt, int)
IMPL_ARR(ArrFloat, float)
ArrInt* arr = ArrIntNew();

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

Написать один раз для void, а затем только объявлять кастующие обертки к нужным типам, да и те только если захочется удобства, ибо зная что ты юзаешь intIntMap, по имени понятно во что кастовать содержимое.

deep-purple
()
Ответ на: комментарий от XMs

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

deep-purple
()
Ответ на: комментарий от pftBest

А как будет выглядеть такой пример со строками в C++

Стандартная библиотека в С++ не очень удобная, это правда (хотя она улучшается от релиза к релизу). В частности в стандартной библиотеке там нет функции split. Но есть boost::split() ; если использовать его, то код будет почти один в один с растовским.

Зато в C++ напирают на итераторы и потоки. Они, конечно, менее читабельны, зато позволяют сделать много чего.

Вот два варианта реализации; один более читабельный, другой менее читабельный. Выбирай.

#include <iostream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <iterator>

using namespace std;

string reverse1(string &input)
{
    string output;
    stringstream oss;
    vector<string> vec;
    string word;
    stringstream iss(input);

    while (getline(iss, word, ' '))
        vec.push_back(word);

    copy(vec.rbegin(), vec.rend()-1, ostream_iterator<string>(oss, " "));
    oss << vec.front();

    return oss.str();
};

string reverse2(string &input)
{
    stringstream oss;
    stringstream iss(input);

    vector<string> vec(istream_iterator<string>{iss}, istream_iterator<string>());

    copy(vec.rbegin(), vec.rend()-1, ostream_iterator<string>(oss, " "));
    oss << vec.front();

    return oss.str();
}


int main()
{
    string text("my little pony, friendship is magic");
    string result = reverse2(text);
    cout << result << endl;

    return 0;
}

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