LINUX.ORG.RU

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

 ,


7

9

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

Надеюсь, речь идет не о синтаксической красоте, так как в этом плане С++ бывает очень уродлив, имхо. Если же говорить о красивых архитектурных решениях, то мне нравится 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 ★★★★★ ()
Ответ на: комментарий от pftBest

Если будут правильные библиотеки, то будет выглядеть практически так же.

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, написанная специально для проекта. Так что никакого контраста не будет.

rj45 ()
Ответ на: 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)
Ответ на: комментарий от deep-purple

Я хотел взять голый STL, но в плане интерфейсов ты знаешь, как я к нему отношусь

XMs ★★★★ ()
Ответ на: комментарий от 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 ★★★★★ ()
Ограничение на отправку комментариев: только для зарегистрированных пользователей