Pull to refresh

Comments 295

В одном из заголовков написано «движимся». Исправьте, пожалуйста.
К сожалению, не нашел в приложении как написать в личку.
В личку
Можно и так
1 Нажать на иконку автора(зайти в его профиль)
2 Кнопка «написать»
Появится в диалогах автора и в Ваших
В андроид приложении если кликнуть на имени автора, то ничего не происходит.
видимо как и в статье, простота использования конфликтует с предоставляемыми возможностями.
То есть или — или. Или теорема о не-существовании серебряной пули
Сигма-тип (тип-сумма, меченное объединение) может содержать значения одного и только одного из нескольких типов.

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

Тип сумма — дизъюнктное объединение исходных типов. То есть мощность множества всех возможных значений объединения типов A и B всегда будет равно сумме мощностей множеств возможных значений A и B, даже если A и B — это один и тот же тип. Поэтому значением типа суммы всегда является метка типа и значение, ведь множества значений разных типов могут пересекаться(1), а значит без метки типа их объединение не является диъюнктным. Так в Haskell, Rust и OCaml тип сумма всегда определяется с использованием конструкторов типа, которые служат теми самыми метками типа при сопоставлении с образцом, например:


enum SumType {
    Int(i64), // конструктор SumType содержащего i64
    String(String), // констуктор SumType содержащего String
    Nothing, // конструктор SumType ~~не~~ содержащего ничего
}

fn main() {
    let string = SumType::String("Hello, World!");
    match string {
        SumType::String(s) => println!("It's a string: {}", s),
        SumType::Int(i) => println!("It's an integer: {}", i),
        SumType::Nothing => println!("It's nothing"),
    };
}

  1. Например множества значений типов Int64 и Float64 в компьютере полностью пересекаются, ибо представлены последовательностью 1 и 0 одного размера.
> конструкторов типа

Конструкторов значений типа, все же. Конструктором типа там чуть-чуть иное называется.
Но не суть.

Всё верно, конструкторы типа это уже из области HKT. Спасибо за замечание.

Не понятен смысл перевода этого материала. Это же формат личного блога: личное мнение автора блога в виде сетований на то, что хотелось бы лучше, а имеем как всегда. Собственно, такая заметка в чьем-то персональном блоге — это нормально. А в виде самостоятельной статьи… В чем постановка задачи/проблемы, в чем решение, какие выводы?

Ну и еще из непонятого (хотя очевидно, что это вопрос не переводчику, но все-таки): а чем C++ные unique_ptr и shared_ptr хуже Rust-овских Box/Rc/Arc? Вроде как тот же самый фаберже, только в профиль.
Мнение автора, а скорее даже подача материала показались мне достаточно интересными, чтобы поделиться ими с русскоязычной аудиторией. Тем более, здесь вроде встречаются переводы статей, выражающих личное (и иногда весьма спорное) мнение авторов.

Более того, в качестве «побочного эффекта», из этой статьи я вынес для себя идею make_visitor, и увидел хороший пример использования variadic using.

По вашему второму вопросу, думаю тут всё дело в умолчании. В Rust для небезопасного разделения владения объектом нужно совершить лишние телодвижения. В C++ наоборот, чтобы приблизить семантику к Rustовской, приходится совершать дополнительные действия, например использовать умные указатели.
Более того, в качестве «побочного эффекта», из этой статьи я вынес для себя идею make_visitor, и увидел хороший пример использования variadic using.
Вроде как все тоже самое расписано в документации к std::visit на cppreference.
В Rust для небезопасного разделения владения объектом нужно совершить лишние телодвижения. В C++ наоборот, чтобы приблизить семантику к Rustовской, приходится совершать дополнительные действия, например использовать умные указатели.
Простите, тут не распарсил. Вы хотите сказать, что в C++ когда нужно иметь ссылку на один и тот же объект, то люди, чтобы не парится с контролем времени жизни, тупо размещают объект в динамической памяти и используют shared_ptr?
Вы хотите сказать, что в C++ когда нужно иметь ссылку на один и тот же объект, то люди, чтобы не парится с контролем времени жизни, тупо размещают объект в динамической памяти и используют shared_ptr?

Я скорее хотел сказать, то в C++ без библиотечного класса приходится париться с временем жизни вручную. То есть умные указатели ничем не хуже рустовской модели памяти, просто ниша у C++ немного другая, как бы разработчики Руста не заявляли о попытках вытеснить C++ из традиционных областей его применения.
И опять не понял. В Rust-е, afaik, типы Box/Rc/Arc так же являются частью стандартной библиотеки языка. И используются для тех же самых целей. Так почему же «и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка»?

Понятно, что это не ваши слова, но раз вы посчитали статью достойной перевода (почему-то), то остается спрашивать у вас.
Ну, во-первых Box редко используется.
Во-вторых ручные Rc/Arc тоже достаточно редко используются, когда нужно разрулить рекурсивные ссыкли (типичная история — реализовать граф на расте).

В остальное время компилятор сам отлично за всем следит. Я писал телеграм-бота, так у меня там один-единственный rc для разделяемого состояния используется во всей программе. Всё остальное полагается на примитивы самого языка, а не стандартные Rc.
Ну, во-первых Box редко используется.
Во-вторых ручные Rc/Arc тоже достаточно редко используются,

Не понятно, как все это соотносится с «и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка»

Я писал телеграм-бота, так у меня там один-единственный rc для разделяемого состояния используется во всей программе.
В C++ не проблема написать программу, в которой программист вообще ничего сам динамически создавать не будет — будет только размещение объектов на стеке или композиция внутри других объектов, а все остальное будет где-то в дебрях библиотек.

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

Видимо разница в том, что у вас семантика как у unique_ptr и shared_ptr, только из коробки. В С++ выбор или забить и использовать сырые указатели, или везде иметь кучу этих самых unique_ptr, что не очень удобно.

В C++ не проблема написать программу, в которой программист вообще ничего сам динамически создавать не будет — будет только размещение объектов на стеке или композиция внутри других объектов, а все остальное будет где-то в дебрях библиотек.

А кто-то говорил про программу без динамических аллокаций?
Видимо разница в том, что у вас семантика как у unique_ptr и shared_ptr, только из коробки.

Вы какими-то криптограммами говорите. Отдельные слова понятны, а что хотели сказать — не ясно.
В С++ выбор или забить и использовать сырые указатели, или везде иметь кучу этих самых unique_ptr, что не очень удобно.
О каком выборе вы говорите? Такое ощущение, что вы ни C++, ни unique_ptr/shared_ptr толком и не видели.
А кто-то говорил про программу без динамических аллокаций?
Простите, может вы бот? Вы тогда понятна невнятность ваших ответов.

Вы привели пример программы на Rust-е, где для управления жизнью объектов вам потребовался всего один Rc. Это вообще не показатель ничего. Как и программы на C++, в которых пользователь с динамической памятью не работает вообще.
везде иметь кучу этих самых unique_ptr, что не очень удобно.
А разве это прямо неудобно? Синтаксис подлиннее, конечно, но это не то, чтобы прямо ужасно было. И статический анализ помогает не забыть нигде.

Ну я за всю Одессу, конечно, не скажу, но "синтаксис подлиннее" для такой базовой вещи, как ссылка — как мне кажется, очень важно. Плюс, насколько я знаю, это накладывает оверхед в ратнайме. А в том же расте компилятор не ведет подсчет ссылок, а просто генерирует drop(x) в некотором месте, где он вывел смерть переменной с учетом всего этого.

Ну я за всю Одессу, конечно, не скажу, но «синтаксис подлиннее» для такой базовой вещи, как ссылка — как мне кажется, очень важно.
Согласен, кому-то это может быть важно.
это накладывает оверхед в ратнайме
unique_ptr имеет оверхед в рантайме только в случае наличия нетривиального делетера — при конструировании его нужно скопировать (один указатель и одно копирование этого указателя). Уничтожение и использование полностью бесплатны.
Про unique понятно. А что про shared?
Посоветуйте, где прочитать про то, как Rust себя ведет в случае разделяемого владения переменной. Потому что я не представляю себе, как можно добиться гарантий без подсчета ссылок. Возможно, я просто чего-то не знаю.
Честно говоря, так и не понял, как можно добиться гарантий без подсчета ссылок. Рассмотрим следующий код (я на Rust ни разу в жизни не писал, могут быть синтаксические ошибки, но, надеюсь, смысл будет ясен):
pub struct S {
    link: & int; // иммутабельная ссылка
};

static mut vec : Vec<S> = None; // глобальный массив объектов, которые будут разделять владение переменной

fn create_vector() {
    vec = Vec::new(); // создаем массив
    let x = 170239; // переменная, на которую будем ссылаться
    for i in 0..10 {
        let s = S::new();
        s.link = &x;
        vec.push(s);
    }
}

fn change_vector() {
    let x = 239017; // другая переменная, некоторые ссылки из массива мы обновим на нее
    for i in 0..20 {
        // обновляем случайный элемент
        let ind = rand::random() % 10;
        let s = S::new();
        s.link = &x;    
        vec[ind] = s;    
    }
}

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

В расте нет мутабельных глобальных переменных, поэтому такой код вообще не соберется


Во-вторых этот код не соберется, потому что x будет уничтожен после выхода из функции create_vector, а dangling pointers запрещены.

В расте нет мутабельных глобальных переменных, поэтому такой код вообще не соберется
Давайте поместим все в класс. Вектор будет его полем, все функции — методами.
Во-вторых этот код не соберется, потому что x будет уничтожен после выхода из функции create_vector, а dangling pointers запрещены.
А как тогда вообще в Rust сделать хоть какое-нибудь разделяемое владение?
А как тогда вообще в Rust сделать хоть какое-нибудь разделяемое владение?

В safe коде Rc<RefCell<T>> или для многопоточности Arc<Mutex<T>> если нужно разделяемое владение с возможностью мутировать. Для иммутабельного разделяемого владения достаточно Rc<T> или Arc<T>. То есть для мутабельного разделяемого владения в safe коде на расте всегда нужен контейнер который в рантайме будет проверять, что никто не захватил ссылку с возможностью мутировать память перед тем, как дать тебе доступ к ней.

Давайте тогда рассмотрим такой код:
pub struct S {
    link: Rc<RefCell<int>>; // иммутабельная ссылка
};

struct S2 {
    mut vec : Vec<S> = None; // глобальный массив объектов, которые будут разделять владение переменной
}

impl S2 {
    fn create_vector() {
        vec = Vec::new(); // создаем массив
        let x = 170239; // переменная, на которую будем ссылаться
        for i in 0..10 {
            let s = S::new();
            s.link = &x;
            vec.push(s);
        }
    }

    fn change_vector() {
        let x = 239017; // другая переменная, некоторые ссылки из массива мы обновим на нее
        for i in 0..20 {
            // обновляем случайный элемент
            let ind = rand::random() % 10;
            let s = S::new();
            s.link = &x;    
            vec[ind] = s;    
        }
    }
}
Будет ли у нас оверхед в этом случае? Насколько я понимаю из слов уважаемого PsyHaSTe, нет. Но как оно тогда работает? Мы же на этапе компиляции не знаем, куда вставлять деструктор числа 170239.

Rc даст оверхед, потому что он внутри занимается именно тем, о чем можно догадаться по аббревиатуре. Я говорил, что нет оверхеда при использовании примитивов языка, голых ссылок, а не контейнеров. В большинстве случаев нужны неизменяемые шарящиеся ссылки, с чем прекрасно справляются &. Если нужен шарящийся мутабельный стейт — то тут уж, конечно, без счетчика и рантайм оверхеда не обойтись.

То есть, ссылки из Rust означают эксклюзивное владение объектом, и бесплатны, так же, как и std::unique_ptr. Rc из Rust дает множественное владение, и не бесплатен, так же, как и std::shared_ptr. Тогда я не понимаю, в чем ваши претензии к c++. Ну да, shared_ptr не бесплатен, ну так его и не надо использовать в местах, где у вас единственный владелец. И даже не из-за производительности, а потому, что читабельность кода страдать будет.
То есть, ссылки из Rust означают эксклюзивное владение объектом, и бесплатны

Нет, не так.

Смотрите, вот допустим я пишу такой код:

fn print<T: Display>(value: &T) { println!("{}", value); }

let a = "123";
let b: i32 = a.parse();
print(&a);
print(&b);


Тут нет рантайм подсчета ссылок, однако у нас проверяются все те же инварианты, что в противном случае проверял бы Rc. В частности, проверяется, что момент вызовов print не существует мутабельной ссылки, которая может поменять данные между вызовами.
Вот как я могу тот же самый код на C++ написать:
template<typename T>
void print(T* value) {
    cout << *value;
}
...
auto a = make_unique<string>("123");
auto b = parse(a.get());
print(a.get());
print(b.get());

Как видите, никакие std::shared_ptr не нужны и все тоже происходит бесплатно. Так чем же std::unique_ptr и std::shared_ptr хуже, чем reference и Rc?
  1. Что будет, если print меняет значение value? Например
    template<typename T>
    void print(T* value) {
    cout << *value;
    *value = 0;
    }
  2. Что будет, если print возвращает переданное значение?
    template<typename T>
    T* print(T* value) {
    cout << *value;
    return value;
    }
    ...
    auto a = make_unique<string>("123");
    auto b = parse(a.get());
    auto cloneda = print(a.get());
    *cloneda = {0};
    print(b.get());
1. Поменяется значение b. Если хочется, чтобы так делать было нельзя, можно передать по значению или добавить const.
2. Вернется не-владеющий указатель на b. Если хочется возвращать владеющий указатель, можно передать владение в print, а потом вернуть его обратно.
Если вы на что-то намекали, я вас не понял.
Ну вот, кто-то минус в карму поставил. Объясните, добрые люди, я правда что-то не так делаю? Вроде же вполне честно пытаюсь разобраться в ситуации.
  1. насколько я знаю, const в С++ очень легко потерять. Хотя, конечно, некоторые гарантии дает
  2. Почему не владеющий? Как по мне это просто сырой указатель, с которым можно делать всё, что угодно.

P.S. Карму выправил, кто минусует — непонятно, как по мне диалог у нас вполне продуктивный.

1. Const можно потерять только посредством const_cast и C-style cast, если я ничего не забыл. Оба механизма запрещены Core C++ Guidelines, пункты ES48, ES49, ES50. По сути, можно считать, что это unsafe код.
2. Core C++ Guidelines, I11. Передача владения по сырому указателю или ссылке запрещена. Если очень хочется (например, нужно поддержать совместимость по ABI со старой библиотекой), нужно использовать бесплатную обертку gsl::owner<T*>.

PS: Я не намекал на это, но спасибо. Мне действительно важно понимать, если я делаю что-то не так, и обычно минусование кармы в этом помогает, но не всегда.
  1. А что насчет времен жизни? Например если мы получаем ссылку и записываем её адрес какой-нибудь глобальной переменной? Я к тому, что время жизни этой ссылки может быть нестатическим.
  2. Ну я выше использовал сырой указатель, каковой и должен использоваться в таком случае (если верить цитате ниже)
    В C++ в подобном примере кода не будет ни unique_ptr, ни shared_ptr. Контроля со стороны компилятора так же не будет, но ведь речь про unique_ptr/shared_ptr.

Ну и вопрос в том, что в примере ( https://habr.com/post/415737/#comment_18835267 ) возвращается шарящаяся ссылка, но она все равно нарушает гарантии, можно случайно сделать видимое изменение в коде, который получил const ptr.

1. Тогда нам нужно решить, будет этот указатель владеющим или нет. Если будет — shared_ptr, поскольку у нас ситуация разделения владения, если нет — обычный указатель или обертки над ним.
2. Если вы хотите передать владение объектом в функцию, то нужно его передать. Поскольку разделяемое владение нам все еще не нужно, это std::unique_ptr. Тогда код будет выглядеть следующим образом:
template<typename T>
unique_ptr<T> print(unique_ptr<T> value) {
    cout << *value;
    return value;
}
...
auto a = make_unique<string>("123");
*a = "456";
auto b = print(move(a));
*a = "789"; // Здесь статический анализ найдет использование объекта после move. Если статический анализ не запускать, то будет UB, за исключением некоторых типов
auto c = print(move(b));


Последнюю фразу не понял. Код, который получил указатель на константную память (не путать с константным указателем!) не может ее изменить. А чтобы получить обычный указатель из указателя на константную память, нужен const_cast. Другой кусок кода может получить указатель на тот же самый кусок памяти как на не-константный, тогда он сможет его менять.

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


  1. Получается, нужно пользоваться сырыми указателями. Это очень неприятно и error-prone.
  2. Нет, я не хочу передать владение. Есть мой ptr которым я владею, и я хочу дать им попользоваться. Ну например передал в поток, который каждую секунду выводит это сообщение в stdout. И после этого мутировать переданную переменную это ошибка. Можно от этого как-то защититься, не передавая владение?

Последнюю фразу не понял. Код, который получил указатель на константную память (не путать с константным указателем!) не может ее изменить. А чтобы получить обычный указатель из указателя на константную память, нужен const_cast. Другой кусок кода может получить указатель на тот же самый кусок памяти как на не-константный, тогда он сможет его менять.

Да, понял, что немного не так выразился. Тут просто еще вопрос скоупинга. Если посмотреть пример ниже, скоуп первого вызова явно ограничен { ... }, поэтому мы после этого можем мутировать переменную. Второй вызов имеет скоупом весь main и после этого мы мутировать переменную уже не можем. То есть нам нужно поведение как const, так и mut не в сигнатуре функции, а в сценарях её использования.

Кажется, я понял, в чем дело. Я с самого начала думал о владении, а оно тут ни при чем, нужно смотреть на мутабельность/иммутабельность.
Получается, что в Rust компилятор дает нам бесплатную гарантию защиты от data races посредством проверки на то, что в любой момент времени у нас будут доступны либо неограниченное количество ссылок на чтение, либо не более одной — на запись (unsafe не рассматриваем, там может любое безумие твориться, это нормально). В C++ такого из коробки нет, если сделать в библиотеке, это не будет бесплатно в рантайме, а если ввести новые сущности (скажем, новые классы ссылок с сигнальными именами), которые обрабатывать в компиляторе/статическом анализаторе — получится уже другой язык.
Это очень интересно, и, на мой взгляд, является весомым преимуществом Rust. Иметь гарантию лучше, чем не иметь.

Перечитал: судя по всему, вы с самого начала именно об этом и писали, но я никак не мог вас понять. Спасибо за терпеливое объяснение.
Получается, что в Rust компилятор дает нам бесплатную гарантию защиты от data races посредством проверки на то, что в любой момент времени у нас будут доступны либо неограниченное количество ссылок на чтение, либо не более одной — на запись

Не совсем так, за весь многопоток отвечают трейты-маркеры Sync и Send, но направление мыслей абсолютно верное.


Очень рад, что мы пришли к пониманию.

Вот как будет выглядеть такой код на Rust:


pub struct S<'a> {
    link: &'a  i32 // иммутабельная ссылка
}

struct S2<'a> {
    vec: Vec<S<'a>> // глобальный массив объектов, которые будут разделять владение переменной
}

impl<'a> S2<'a> {
    fn create_vector(&mut self) {
        self.vec = Vec::new(); // создаем массив
        let x = 170239; // переменная, на которую будем ссылаться
        for _ in 0..10 {
            let s = S {
                link: &x
            };
            self.vec.push(s);
        }
    }

    fn change_vector(&mut self) {
        let x = 239017; // другая переменная, некоторые ссылки из массива мы обновим на нее
        for _ in 0..20 {
            // обновляем случайный элемент
            let ind = rand::random::<usize>() % 10;
            let s = S {
                link: &x
            };
            self.vec[ind] = s;    
        }
    }
}

https://play.rust-lang.org/?gist=dd87625906cb942982ab1b4388695f2d&version=stable&mode=debug&edition=2015


И как вы может убедиться сами, несовпадение времен жизни ссылок приведут к ошибкам компиляции:


error[E0597]: `x` does not live long enough
  --> src/main.rs:17:24
   |
17 |                 link: &x
   |                        ^ borrowed value does not live long enough
...
21 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the impl at 11:1...
  --> src/main.rs:11:1
   |
11 | impl<'a> S2<'a> {
   | ^^^^^^^^^^^^^^^
Да, мне уже указали, что для разделяемого владения нужен Rc.
Ну я за всю Одессу, конечно, не скажу, но «синтаксис подлиннее» для такой базовой вещи, как ссылка — как мне кажется, очень важно.
Вас может удивить, но в C++ такая базовая вещи, как ссылка, имеет очень компактный синтаксис. А unique_ptr/shared_ptr — это вовсе не замена ссылкам.
А в том же расте компилятор не ведет подсчет ссылок, а просто генерирует drop(x) в некотором месте, где он вывел смерть переменной с учетом всего этого.
Представляете, в C++ точно так же!
vector<MyObject> make_vector() {...}
void f() {
  vector<MyObject> objects = make_vector();
  ...
} // Здесь компилятор вызовет деструктор для objects без какого-либо подсчета ссылок.

Есть ощущение, что вы просто не знаете, где и для чего в C++ используются умные указатели вообще и unique_ptr/shared_ptr в частности.
И кто помешает вернуть указатель на objects который будет удален в конце функции? Если мы юзаем умные указатели, то они проконтролируют этот момент. а без них как? Раз я «не понимаю», то может, разъясните?
У вас есть функция:
vector<MyObject> make_vector() {...}
Зачем вам возвращать указатель?

Что мне помешает написать такой код?


MyObject* f() {
  vector<MyObject> objects = make_vector();
  return objects[0];
}
В принципе, ничего. Но этот код не имеет отношения к предназначению и использованию unique_ptr/shared_ptr.
здравый смысл. В расте можно написать такую же белиберду в unsafe блоке, но почему-то же никто (надеюсь) так не делает?

The problem is not the problem. The problem is your attitude about the problem. Do you understand? © Jack Sparrow
Здравый смысл это да, но тот же пример статей PVS studio говорит, что программисты ошибаются. Тот же эффект последней строки тоже казалось бы невозможен, ведь «здравый смысл» подсказывает, как надо было написать. Но это происходит на практике.

Поэтому компилятор, который отлавливает больше подобных ошибок — хороший. Ну и тот факт, что блок будет помечен как unsafe сильно снижает как область поиска проблемного места, так и вероятность того, что код вообще пройдет ревью.
PsyHaSTe, так а что с темой unique_ptr? Ваш пример здесь не в тему, ибо в C++, как и в Rust-е, вы не можете возвращать ссылку/указатель на член локального вектора. Только в C++ вам за это по рукам никто не бьет, а в Rust-е вам на это компилятор укажет.

Но фокус-то в том, что unqiue_ptr здесь вообще не при чем. Если же вы считаете, что при чем, то покажите, как вы собираетесь решить эту проблему с помощью unique_ptr в C++.

Или вы опять ограничитесь минусами, а сами сольетесь в очередной раз?
При чем тут unique_ptr? У меня к нему изначально вопросов не было, у меня был вопрос про shared.

Только в C++ вам за это по рукам никто не бьет, а в Rust-е вам на это компилятор укажет.

Ну в этом собственно и суть, чтобы компилятор дал по рукам. Если дает — хороший, годный компилятор, если нет — то не очень.

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

Я, конечно, дико извиняюсь, но я вообще минусами очень редко пользуюсь, даже в комментариях. Полагаю, что читателям просто нравится переходы на личности и наезды в дворовом стиле «а ты не бот ли часом?»/«чет опять слился»/etc
image
При чем тут unique_ptr? У меня к нему изначально вопросов не было, у меня был вопрос про shared.
Во-первых, есть ощущение, что вы не понимаете ни предназначение unique_ptr, ни shared_ptr. Поэтому для упрощения разговора попытался с вами поговорить хотя бы про unique_ptr.

Во-вторых, вы не даете себе труда отвечать на вопросы и уточнять те ваши туманные высказывания, которые не понятны собеседнику. В частности, это ведь вы написали:
Видимо разница в том, что у вас семантика как у unique_ptr и shared_ptr, только из коробки. В С++ выбор или забить и использовать сырые указатели, или везде иметь кучу этих самых unique_ptr, что не очень удобно.
Не сочтите за труд, объясните, о чем вы здесь пытались сказать. Ибо не понятно от слова совсем.

В-третьих, вы указали на «проблемный код»:
MyObject* f() {
  vector<MyObject> objects = make_vector();
  return objects[0];
}

Покажите, как вы хотите сделать его «нормальным» с помощью unique_ptr/shared_ptr. И аналог из Rust-а. Чтобы можно было предметно показать вам, в чем вы заблуждаетесь.

Отрадно, что это не вы решили отвечать минусами. Плохо то, что вы сливаетесь и не отвечаете.
Во-первых, есть ощущение, что вы не понимаете ни предназначение unique_ptr, ни shared_ptr. Поэтому для упрощения разговора попытался с вами поговорить хотя бы про unique_ptr.

Я говорю с позиции, что unique ptr — это уникальный указатель, который может быть только один, и передает владение при передаче, а shared_ptr, соответственно, позволяет раздавать равноправный доступ. Могу ошибаться, я в последние стандарты плюсов не лез, для меня в свое время откровением было появление лямбд и auto.


Не сочтите за труд, объясните, о чем вы здесь пытались сказать. Ибо не понятно от слова совсем.

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


Отрадно, что это не вы решили отвечать минусами. Плохо то, что вы сливаетесь и не отвечаете.

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

Знаете, я устал уже отвечать на важе «че слился» в десятый раз. Всего доброго, честь имею.
По факту, вы не сказали ничего, что могло бы прояснить фразу: «и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка» Хотя именно в ее обсуждение вы и попытались влезть.

В сухом остатке: вы не смогли рассказать, в чем отличия unique_ptr/shared_ptr от Box/Rc/Arc. И, уж тем более, не смогли показать, чем unique_ptr/shared_ptr хуже.

Более того, как только разговор заходил более-менее предметно, вы переставали отвечать.

Что же это тогда, как не слив? Как бы вы к этому высказыванию не относились.
В сухом остатке: вы не смогли рассказать, в чем отличия unique_ptr/shared_ptr от Box/Rc/Arc. И, уж тем более, не смогли показать, чем unique_ptr/shared_ptr хуже.

Постараюсь последний раз объяснить.

shared_ptr я так понимаю позволяет шарить стейт с Rc — ОК.

голый указатель позволяет не иметь оверхеда — ОК.

Проблема в том, что если мы хотим разделяемый неизменяемый стейт, у нас есть два механизма — надежный, но с рантайм оверхедом, и ненадежный без оверхеда.

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

Rc/Arc это тот же shared_ptr с рантайм оверхедом, никакой магии там нет. Разница лишь в том, что они нужны в очень малом проценте задачи и/или ситуаций. В остальном отлично справляются языковые ссылки `&` без оверхеда.

Что касается Box, то он вообще ортогонален обсуждаемой теме, и используется в основном для динамической диспетчеризации.
Постараюсь последний раз объяснить.
Вы в очередной раз не смогли. Вопрос был вполне конкретный: чем unique_ptr/shared_ptr хуже Box/Rc/Arc. Ответа нет.

Рассуждения о том, что вы можете что-то дропать, во-первых, хорошо было бы подтверждать примерами кода. Ибо верить вам на слово поводов нет. И, во-вторых, рассуждения эти не имеют отношения к тому, что меня лично интересовало.
Вы в очередной раз не смогли. Вопрос был вполне конкретный: чем unique_ptr/shared_ptr хуже Box/Rc/Arc. Ответа нет.

Я выше на пальцах объяснил хотя бы тот факт, что Box не имеет отношения к рассматриваемому вопросу. Точно так же вы видимо полностью проигнрировали тот факт, что Rc это и есть растовый shared_ptr. Наконец, вопрос «лучшести»/«хужести», поднятый автором, я так понимаю, сводится к тому, что shared_ptr является оверкиллом во сногих случаях, когда можно обойтись просто передачей ссылки без подсчета.

Ибо верить вам на слово поводов нет.

Мы не в церкви, чтобы верить, чего я и не прошу, собственно. Я выше давал ссылку на спеку, где написано, что и как работает. Если надо, могу дать ссылку на репозиторий компилятора, где можно самому посмотреть, что да как реально происходит.
Я выше на пальцах объяснил хотя бы тот факт, что Box не имеет отношения к рассматриваемому вопросу.
Нет. Либо вы опять о чем-то своем. Не вижу отличий Rust-овского Box-а от плюсового unique_ptr.
Точно так же вы видимо полностью проигнрировали тот факт, что Rc это и есть растовый shared_ptr.
Вы ошибаетесь. Я в курсе, что такое Rc и Arc, чем они похожи, а чем отличаются от shared_ptr.

И, зная это, мне сложно понять, чем shared_ptr хуже Rc/Arc.
Наконец, вопрос «лучшести»/«хужести», поднятый автором, я так понимаю, сводится к тому, что shared_ptr является оверкиллом во сногих случаях, когда можно обойтись просто передачей ссылки без подсчета.
Так вот я вам в очередной раз и говорю: вы не понимаете. А чтобы говорить с вами предметно, нужно оперировать примерами кода. Коих вы привести не можете.
Мы не в церкви, чтобы верить, чего я и не прошу, собственно.
Достаточно привести пример кода, в котором компилятор Rust-а вывел бы что-то без подсчета ссылок. Но пока примеры кода не по вашей части, увы.
Так вот я вам в очередной раз и говорю: вы не понимаете. А чтобы говорить с вами предметно, нужно оперировать примерами кода. Коих вы привести не можете.

Ок, вот пример кода:


use std::fmt::Display;

fn print_and_get_reference<T: Display>(value: &T) -> &T {
    println!("{}", value);
    &value
}

fn main() {
    let mut test_value = "123";
    {
        let _foo = print_and_get_reference(&test_value);
    }
    test_value = "456";
    let _bar = print_and_get_reference(&test_value);
    test_value = "789"; // cannot assign to `test_value` because it is borrowed
    let _baz = print_and_get_reference(&test_value);
}

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


Достаточно привести пример кода, в котором компилятор Rust-а вывел бы что-то без подсчета ссылок. Но пока примеры кода не по вашей части, увы.

Я уже немного утомился от постоянных переходов на личности. Может хватит подобной низкопробной демагогии? Она вас не красит.

Тут происходит проверка референсов и оказывается, что мы чуть не поменяли значение, которое менять мы не хотели.
Послушайте, ну ведь это же не имеет отношения к тому, для чего применяются unique_ptr/shared_ptr в C++ (см. здесь). Так что к ответу на вопрос о том, почему unique_ptr/shared_ptr можно считать плохой шуткой, мы не приближаемся. В C++ в подобном примере кода не будет ни unique_ptr, ни shared_ptr. Контроля со стороны компилятора так же не будет, но ведь речь про unique_ptr/shared_ptr.
Может хватит подобной низкопробной демагогии?
Может надо было сразу переходить к примерам?
В C++ в подобном примере кода не будет ни unique_ptr, ни shared_ptr. Контроля со стороны компилятора так же не будет, но ведь речь про unique_ptr/shared_ptr.

Ну как это. Мы говорили о том, как нам в С++ железно получит подобный контроль. Мы не говорили "а давайте просто сырые указатели передавать и пусть программист сам контролирует время жизни".

Мы говорили о том, как нам в С++ железно получит подобный контроль
Так вы и не получите в C++ подобный контроль. Ни с умными указателями, ни без. Или вы сможете?
Мы не говорили «а давайте просто сырые указатели передавать и пусть программист сам контролирует время жизни».
Простите, но я вообще не помню, такой темы в нашем разговоре. Дайте ссылку где она всплыла.

На всякий случай: в современном C++ голый указатель рассматривается как невладеющий. Т.е. дергать для него delete не рекомендуется. Предполагается, что время жизни контролирует тот, кто предоставил вам голый указатель.
Так вы и не получите в C++ подобный контроль. Ни с умными указателями, ни без. Или вы сможете?

Разве повсеместное использование shared_ptr для ВСЕХ указателей не даст подобного контроля?


На всякий случай: в современном C++ голый указатель рассматривается как невладеющий. Т.е. дергать для него delete не рекомендуется. Предполагается, что время жизни контролирует тот, кто предоставил вам голый указатель.

благодарю, буду иметь в виду.

Разве повсеместное использование shared_ptr для ВСЕХ указателей не даст подобного контроля?
Такого контроля, как вы показали выше, когда компилятор бьет по рукам за изменение объекта, на который кто-то уже ссылается, — нет. Более того, в C++ никто вам не запретит сделать так:
auto sptr1 = std::make_shared<int>(42);
auto sptr2 = sptr1;
delete sptr1.get();
*sptr2 = 42;
Ошибку вы получите только в run-time. И то, если вам повезет упасть сразу, а не тихо запортить память.
Я думаю, что в нашем диалоге было много непонимания с обеих сторон. К сожалению, по вашим формулировкам в начале я не понимал, о чем именно вы говорите. Полагаю, вы заблуждались на счет того, для чего в современном C++ применяются shared_ptr и какие гарантии они могут дать.

К сожалению, местами shared_ptr используют неуместно. Это затрудняет разговор о достоинствах и недостатках unique_ptr/shared_ptr в C++.
И, во-вторых, рассуждения эти не имеют отношения к тому, что меня лично интересовало.

Какой раз вы пытаетесь акцентировать диалог в том месте где вам удобно это делать, я уже, честно признаться, со счёта сбился. Но вообще это так не работает, никакая языковая фича не существует изолированно, каждая из них используется вместе с другими фичами и именно то как она используется вместе с другими фичами важно. Вы же упорно отказываетесь так вести диалог, упёрлись в своего сферического коня в вакууме и всё пытаетесь выведать чем же наш конь от вашего отличается. Так вот — отличается он именно фичами, с которыми этот конь взаимодействует и тем, как он с ними взаимодействует. Сферические же кони в вакууме интересны только теоретикам, а на практике они никакой роли играют, а ведь именно вы громче всех голосовали о практической ценности.

Ничего подобного. Речь изначально шла о том, что unique_ptr/shared_ptr объявлялись ущербными по сравнению с тем, что есть в Rust-е. В Rust-е из аналогичных средств есть Box/Rc/Arc. И пока никто из растоманов не смог объяснить, чем одно лучше/хуже другого.

Растоманы пытаются перевести стрелки на контроль времени жизни, который жестко контролируется в Rust-е. Но, видимо, из-за недостатка опыта в C++, не понимают, для чего в C++ используются unique_ptr/shared_ptr. А из-за этого непонимания с растоманами не получается вести конструктивный диалог.
из-за недостатка опыта в C++

Видимо у вас из-за недостатка знаний Rust не получается понять.


У меня опыта в С++ больше, чем в Rust, но я прекрасно понимаю, о чем они говорят. Из чего я делаю вывод, что дело в вас. Вам бы не помешало поднять уровень знаний, сударь.

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

Да практически все достижения человечества за вычетом жалких 0.000001%. И то я сомневаюсь, что вы способны осознать подобные объемы информации.


Вы сами должны определиться, чего вы не понимаете. Я ж не психолог.

От ведь.

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

В C++ нормальное применение unique_ptr/shared_ptr — это управление жизнью динамически созданными объектами. По каким-то причинам объект пришлось создать в хипе и хочется избежать ручного управления временем его жизни за счет плюсовых конструкторов/деструкторов. По каким причинам пришлось создавать объект в хипе — не суть важно. Скорее всего, объект просто должен пережить скоуп, в котором создан. Хотя здесь может быть все веселее за счет фабрик и прочих ООП-ных заморочек, но это уже ненужные детали.

Ключевой момент в том, что нормальное применение unique_ptr/shared_ptr — это контроль жизни динамически созданного объекта. Unique_ptr для простых случаев, когда владелец один. Shared_ptr для более сложных ситуаций, когда владельцев несколько и нет возможности статически разрулить время владения.

Так вот, насколько я понимаю, в Rust-е с использованием Box/Rc/Arc все в точности тоже самое. За исключением деталей вроде отсутствия атомарного счетчика ссылок в Rc.

Ненормальное использование shared_ptr в C++ (именно shared_ptr, ибо со случаями подобного использования unique_ptr сталкиваться не доводилось) — это когда людям лень разбираться со временем владения объекта и они тупо заворачивают объект в shared_ptr. Особенно часто такое встречается с людьми, которые пришли в C++ из языков с GC. Вот в их коде запросто можно увидеть что-то вроде:
std::shared_ptr<std::vector<MyObject>> make_vector() {...};

или даже:
std::shared_ptr<std::vector<std::shared_ptr<MyObject>> make_vector() {...}

Хотя вполне можно было бы обойтись самым простым:
std::vector<MyObject> make_vector() {...}

Так вот у меня есть ощущение, что оппонирующие мне растоманы пытаются перевести разговор именно на подобное использование shared_ptr. Мол, там, где в C++ люди для простоты берутся за shared_ptr, в Rust-е можно полагаться на контроль времени жизни объектов.

Итак: правильно ли я понимаю то, о чем пытаются здесь говорить растоманы?
Ключевой момент в том, что нормальное применение unique_ptr/shared_ptr — это контроль жизни динамически созданного объекта.

Отвечаю как программист с опытом С++: на самом деле это не так. Это умные указатели. Они не контролируют время жизни, по Александреску они созданы для дереференса и для вызова правильного деструктора либо после разрушения переменной(unique_ptr), либо по достижению счетчика нуля(shared_ptr). Ну и в какой-то степени unique_ptr можно считать частным случаем shared_ptr.


Вопрос: почему они не контролируют время жизни объекта? Потому что RAII хорош только в том случае, когда ты используешь так называемые "правила хорошего тона". Вот как можно сломать unique_ptr:


#include <memory>

int main () {
  int* p = new int (10);
  std::unique_ptr<int> a (p);
  std::unique_ptr<int> b (p);
}

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


Отвечаю как программист с опытом Rust: время жизни объектов контролирует сам язык, поэтому вышеуказанная ситуация в Rust в принципе не возможна. К примеру, Box используется для размещения объекта в куче (и вызове free при разрушении; почти как unique_ptr):


fn main() {
    let p = String::from("10");
    let a = Box::new(p);
    let b = Box::new(p);
}

Программа не соберется с ошибкой:


error[E0382]: use of moved value: `p`
 --> src/main.rs:4:22
  |
3 |     let a = Box::new(p);
  |                      - value moved here
4 |     let b = Box::new(p);
  |                      ^ value used here after move
  |

Вот что такое контроль времени жизни объекта. К Box/Rc/Arc это не имеет никакого отношения. Это вшито в язык, а не в библиотечный код.


И опять не понял. В Rust-е, afaik, типы Box/Rc/Arc так же являются частью стандартной библиотеки языка. И используются для тех же самых целей. Так почему же «и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка»?

Теперь можете сами попробовать почувствовать, шутка или нет.

Это умные указатели. Они не контролируют время жизни, по Александреску они созданы для дереференса и для вызова правильного деструктора либо после разрушения переменной(unique_ptr), либо по достижению счетчика нуля(shared_ptr).
Приплыли. Умные указатели, которые не контролируют время жизни, но вызывают деструктор…
Умные указатели, которые не контролируют время жизни, но вызывают деструктор…

А что по-вашему есть время жизни?

А что по-вашему есть время жизни?
В С++ — это время между инициализацией (вызовом конструктора) и деинициализацией (завершением деструктора) объекта. Для статических, автоматических объектов и членов агрегатов (C-шных массивов, членов структур/классов) это время контролируется языком (компилятором/ран-таймом). Для объектов, созданных через new (обычный, перегруженный или placement new), время жизни контролирует пользователь. Умные указатели как раз созданы для упрощения этого контроля.

Ну а ваш Rust-овский пример, как и ряд других примеров, упомянутых в обсуждении unique_ptr/shared_ptr vs Box/Rc/Arc, нерелевантен, поскольку он показывает общие свойства языка Rust. Вы можете убрать оттуда Box и все равно будет продолжать показывать те же самые свойства:
fn take_and_show(w: String) {
    println!("value: {}", w);
}
fn main() {
    let p = String::from("Bla-bla-bla");
    take_and_show(p);
    take_and_show(p);
}

Между тем, вопрос, который меня интересует, состоит именно в том, почему unique_ptr/shared_ptr объявлены неудачной шуткой.
В С++ — это время между инициализацией (вызовом конструктора) и деинициализацией (завершением деструктора) объекта.

И как же умные указатели могут контролировать время жизни? :D

Вызывают деструктор подконтрольного им объекта в подходящее время (в собственном деструкторе в случае unique_ptr, при обнулении счетчика сильных ссылок в случае shared_ptr).
Вызывают деструктор подконтрольного им объекта в подходящее время

Ну вот в этом и проблема. Вы можете вызвать деструктор, а переменная еще будет существовать, что может привести ко всяким веселым последствиям. Какой же это контроль?


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

Какой же это контроль?
Обычный для C++.

Ну так и скажите, что это дырявый контроль, если у тебя умерла переменная на руках, а ты этого не узнаешь и с этим ничего сделать не можешь. Ой, можешь совершить UB, можешь кинуть сигнал и упасть в рантайме))) Мощный выбор.

Ну так и скажите, что это дырявый контроль,
Простите, но разве где-то утверждалось обратное?

Вы упорно уводите разговор в сторону. Посему напомню еще раз вопрос, на который я хотел бы получить ответ, но не могу: почему unique_ptr/shared_ptr являются неудачной шуткой?

И дабы не заводить шарманку с перечислением достоинств Rust-а, ограничу вас. Я понимаю, что дает разработчикам Rust, как компилятор Rust-а может следить за жизнью переменных, как он контролирует ссылки на объект и пр.

Только вот ссылки Rust-а, borrow checker, lifetimes и пр. следует противопоставлять ссылкам/указателям в C++ и отсутствию этих самых borrow checker-ов, lifetimes и пр. в C++.

Тогда как unique_ptr/shared_ptr в C++ появляются в особых случаях (о которых речь уже была) и служат вполне конкретным целям. Таким же, насколько я могу судить, как и Box/Rc/Arc в Rust-е. Так в чем ущебность unique_ptr/shared_ptr?

Такое ощущение, что лишь тем, что unique_ptr/shared_ptr в С++.
А вот если сравнивать сами unique_ptr и Box, то чем один лучше другого?

Я понимаю ваш вопрос, особенно в контексте:


Между тем, вопрос, который меня интересует, состоит именно в том, почему unique_ptr/shared_ptr объявлены неудачной шуткой.

И да, я согласен с вами, что общие свойства unique_ptr и Box одинаковые, ни один не лучше другого. Но стоит смотреть на это через призму Луны языка, а именно:


В C++ все небезопасно, это его идеология.

То получается, что unique_ptr/shared_ptr ни за что и не отвечают. В книгах их преподносят как панацею(детишки, не трогайте new/malloc), а на деле они не контролируют время жизни, они периодически вызывают деструкторы, а ты должен молиться всем богам, чтобы у тебя не было живых ссылок на объект в момент вызова деструктора.


Тогда как unique_ptr/shared_ptr в C++ появляются в особых случаях (о которых речь уже была) и служат вполне конкретным целям.

Простите, каким конкретным целям? Я что-то не видел этого в дискуссии. Либо это было наброшено вами без аргументов.


Выдержка из Александреску(Современное проектирование на С++, глава 7 "Интеллектуальные указатели", стр 179):


Интеллектуальные указатели — это объекты языка С++, имитирующие обычные указатели с помощью реализации оператора -> и унарного оператора *. Кроме того, они часто скрытно выполняют полезные задания (например, осуществляют управление памятью или блокировку)

А теперь сравните это с вашим оскорбительным тоном:


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

Учите матчасть, прежде чем спорить.

То получается, что unique_ptr/shared_ptr ни за что и не отвечают.
Вы путаете. Они отвечают, но с гарантиями у них так же, как и во всем остальном C++.
В книгах их преподносят как панацею(детишки, не трогайте new/malloc)
Полагаю, здесь имеет место случай, когда вы прочитали невнимательно и проинтерпритировали по своему.
Я что-то не видел этого в дискуссии.
Я был бы признателен, если бы вы читали то, что пишут именно вам. Особенно, если бы вы читали это внимательно.

Ссылки на Александреску не котируются, ибо:

1. Умные указатели появились и начали использоваться задолго до Modern C++ Design.

2. Есть ненулевая вероятность, что вы интерпритируете слова по-своему и выдергиваете их из контекста.
Расскажите, пожалуйста, что же именно я не понимаю. Это не сарказм, это действительно не понимание того, почему диалог с растоманами не складывается.

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

Проблема в том, что я веду речь именно об unique_ptr/shared_ptr. А не о сферических интеллектуальных указателях в вакууме из книги Александреску. Попробуйте показать, как с помощью unique_ptr/shared_ptr сделать что-то кроме управления временем жизни ресурса, тогда, может быть, можно будет и расширить контекст разговора (хотя нет).
Проблема в том, что вы считаете, что умные указатели управляют временем жизнью переменной.
И не временем жизни переменной, а моментом вызова deleter-а для подконтрольного указателя (значения в общем случае). Время жизни переменных контролирует компилятор.
И не временем жизни переменной, а моментом вызова deleter-а для подконтрольного указателя (значения в общем случае). Время жизни переменных контролирует компилятор.

Я рад, что вы постепенно признаете свои ошибки.


Попробуйте показать, как с помощью unique_ptr/shared_ptr сделать что-то кроме управления временем жизни ресурса

Лихко. Это даже забавно, как Дима Шарихин приводит в плюсы недостатки проектирования, которыми я воспользуюсь:


template<typename T>
using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;

deleted_unique_ptr<FILE> file(
    fopen("file.txt", "r"),
    [](FILE*) { printf("Fuck you"); });

Вот видите, этот умный указатель вместо управления ресурсами указывает вам направление, куда стоит пойти(тут у вас в голове должны раздаться звуки Дорожной).

Вот видите, этот умный указатель вместо управления ресурсами указывает вам направление, куда стоит пойти.
Ваше желание схамить скрывает от вас тот простой факт, что файл — это ресурс. И управление им происходит по той же самой схеме, что и разрушение динамически созданных объектов.
И управление им происходит по той же самой схеме, что и разрушение динамически созданных объектов.

В вышеуказанном коде управления не происходит. Вы скомпилируйте эту программу и запускайте ее до осознания происходящего.

В вышеуказанном коде управления не происходит.
Происходит. Вы пообещали, что печать фразы «F**k you» освободит ресурс. Компилятор и stdlib вам поверили. То, что вы пообещали ерунду никак не влияет на то, что unique_ptr честно попытался ресурс освободить.

Тем не менее, похоже, что упражнения в утонченном хамстве — это все, что вы можете сказать по исходному вопросу.
утонченном хамстве

Спасибо, я знал, что вы оцените)))


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

Ну вот мы с вами и сошлись во мнении. И unique_ptr/shared_ptr, и Box/Rc/Arc — это все одно и то же. Разница в языках. И как показывает практика и вышеуказанные примеры, разница не в пользу С++.

И unique_ptr/shared_ptr, и Box/Rc/Arc — это все одно и то же.
Таким образом фраза «Поиграйте немного с Rust, и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка.» остается необъясненной. Ибо что неудачного в unique_ptr/shared_ptr, если тоже самое пришлось сделать в Rust-е, непонятно.

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

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


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

и наполнил вашу голову новыми знаниями
Ничего нового вы мне здесь не открыли.
И получил за это Ушат Помоев
Ваша мнительность может дать фору вашей дерзости.
Ничего нового вы мне здесь не открыли.

То есть вы на пустом месте раздуваете конфликт, устраиваете демагогию? Это неприлично.


Если вы все же не являетесь троллем, прошу в gitter.

То есть вы на пустом месте раздуваете конфликт, устраиваете демагогию?
Скорее всего, вы просто не понимаете сути моего вопроса.
прошу в gitter
Зачем? Я прекрасно знаю, в чем Rust лучше C++, знаю условия, при которых я предпочту один язык другому. И эти знания не позволяют мне понять претензии к unique_ptr/shared_ptr, высказанные в статье. Вряд ли вы сможете объяснить мне суть этих претензий.

Я вам первым примером показал проблемы умных указателей в С++. Они не владеют объектами и не управляют временем жизни. Они держат указатель и в нужный момент вызывают делитер. Поэтому можно создать 2 умных указателя на один ресурс и получить выстрел в ногу. Или с помощью метода функции-члена ::get получить сырой указатель, который окажется у вас на руках после смерти умного указателя.


Вам мало минусов? Вы их упорно не хотите замечать?

Вам мало минусов? Вы их упорно не хотите замечать?
Это не минусы конкретно unique_ptr/shared_ptr. Это принципиальные особенности C++ — компилятор верит, что вы знаете, что вы делаете. Если вы хотите засунуть один и тот же указатель в два unique_ptr, то значит вам это нужно. Равно и как использовать возвращаемое значение get() в качестве владеющего указателя. А если вам этого не нужно, то, вероятно, вам не нужен и сам C++.

Так что все эти претензии мимо собственно умных указателей из C++ной stdlib.

Раз вы не понимаете сути моего вопроса, то постараюсь объяснить на пальцах: между C++ и Rust-ом овердофига различий. И зная хотя бы часть из них, можно было бы сформулировать мысль про «неудачную шутку» так, чтобы она не вызывала вообще никаких вопросов. Вот с ходу пару-тройку примеров:

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

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

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

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

Ну и собственно, вопрос: почему при всем многообразии понятных и очевидных претензий, неудачной шуткой были признаны именно unique_ptr и shared_ptr?

Ведь как раз unique_ptr/shared_ptr — это одно из самых удачных и вменяемых расширений C++ной stdlib. К ним вообще практически нет объективных претензий. За исключением, может быть, того, что в shared_ptr атомарные счетчики ссылок прибиты намертво гвоздями.
Это не минусы конкретно unique_ptr/shared_ptr. Это принципиальные особенности C++

Они написаны на C++. Вы постоянно об этом забываете.

Они написаны на C++.
Т.е. неудачность шутки в том, что классы для C++ной stdlib пишутся на C++. Да еще во времена, когда над зачаточной версией Rust-а работал один-единственный энтузиаст.

Действительно, штука так себе.
a) В Rust они не могут быть NULL б) нельзя создать два независимых умных указателя на один объект.

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

По поводу NULL — это спорно. Нулевой указатель — это вполне себе нормальная вещь. А вот надобность от нее прятаться за Optional… Да еще в низкоуровневом языке…
А вот надобность от нее прятаться за Optional… Да еще в низкоуровневом языке…


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

Так что для низкоуровневого кода спорно. Для высокоуровневого прикладного спорно применение C++ вообще.

Почему не нужен-то? Если вы думаете, что раст таскает за собой теги Option'а и это приводит к лишнему оверхеду, то это не так, он даже сложные случаи вроде вложенных Either<T1,Either<T2,Either<T3,Either<T4,T5>>>> может обрабатывать. Зато на уровне типов сразу видно, где проверка необходима, а где её не должно быть

Потому, что вы не можете взять значение из Optional без проверки. Если можете, то смысл в Optional теряется. Вот, например, в плюсовом std::optional есть operator*, который проверку не делает. В этом случае никакой безопасности вы не получаете, сами несете за все ответственность. И есть метод value(), который проверку делает. Но это не бесплатно.

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

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


В этом случае никакой безопасности вы не получаете, сами несете за все ответственность.

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


Ну и речь не про оверхэд по данным, а оверхэд на проверку наличия данных при доступе к ним.

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


Ну вот вы пишете на расте:


fn foo(value: Option<Something>) {
   if let Some(value) = value { 
      value.bar();
   }
}

ну ничем это не отличается от


void foo(Something* value) {
   if (value) {
      value.bar();
   }
}
Я не хочу никакой ответственности, я хочу, чтобы у компилятора болела голова за такие вещи, а я бизнес-логику писал.
Тогда зачем вам C++ и что вы обсуждаете в теме про C++?
где вы сами бы их и написали
Простите, но если у вас есть:
fn some_low_level_code(o: Option<Box<Object>>) {...}

то вы не можете внутри взять содержимое o без проверки. Даже если вы знаете, что объект там есть. Следовательно, вы будете делать проверку в том или ином виде (match, unwrap или еще что-то). А если вы хотите этой проверки избежать, то вы где-то в одном месте сделаете проверку, возьмете ссылку и дальше будете оперировать только ссылкой.

Собственно, в C++ вы будете делать тоже самое. Просто у вас unique_ptr изначально будет чем-то вроде Option<Box<T>>.
Тогда зачем вам C++ и что вы обсуждаете в теме про C++?

Потому что у нас сравнительное обсуждение пользы от явного Option в аргументах, разве нет?


вы не можете внутри взять содержимое o без проверки. Даже если вы знаете, что объект там есть.

Если я знаю, что объект точно есть, то я напишу:


fn some_low_level_code(o: &Object) {...}

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


И да, Box тоже почти не используется, не надо его писать в примеры :)

Потому что у нас сравнительное обсуждение пользы от явного Option в аргументах, разве нет?
Нет. Я надеюсь, мы все еще выясняем, почему unique_ptr/shared_ptr в С++ — это неудачная шутка.

Очередное объяснение — это nullability для unique_ptr/shared_ptr. Как по мне, это не баг, а фича. Т.к. нулевое значение для указателя — это его фундаментальное свойство в C++. Для тех, кто хочет иметь non_null указатели, есть gsl::not_null.
Нет. Я надеюсь, мы все еще выясняем, почему unique_ptr/shared_ptr в С++ — это неудачная шутка.

У меня предложение — как насчет написать автору и узнать у него? Потому как мне кажется, что он мог просто неправильно подать мысль, на что вы отвечаете «нет, он бы сказал тогда иначе». Стоит связаться с первоисточником, чтобы не плодить стены не очень полезного текста.

Очередное объяснение — это nullability для unique_ptr/shared_ptr. Как по мне, это не баг, а фича.


Я категорически не согласен с подобной позицией, но мне она ясна. Спасибо.
Стоит связаться с первоисточником, чтобы не плодить стены не очень полезного текста.
ИМХО, это задача того, кто сюда притащил перевод :)

ИМХО, это задача того, у кого возник такой вопрос и кому это интересно, то есть это твоя задача.

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

Прикол Option в том, что вы в одном месте можете провереть его на None, достать из него значение, а потом пользоваться им как non-nullable указателем. Option нужен в низкоуровневом языке, чтобы избежать лишних проверок на null там, где они не нужны и не забыть их сделать там, где они нужны. Это можно сказать та самая защита от логических ошибок, которую даёт строгая статическая типизация.

Вы можете сделать тоже самое и с unique_ptr. Так что шило на мыло.

Option<&T> не является владеющим указателем, в отличии от unique_ptr, более того, Option<&T> даже "умным" указателем не является, ибо оптимизируется в обычный сырой указатель и имеет значение только на этапе проверки схождения типов.

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

Вы упорно не хотите понимать контракты, которые дают Option или иные типы. Почему-то вы считаете, что в низкоуровневом языке высокоуровневые абстракции не нужны. NonNull не нужен. Option (который возвращается в результате NonNull::new) тоже не нужен.
Вы похожи на верунов, которые отказываются от современных достижений в пользу собственных заблуждений.

Почему-то вы считаете, что в низкоуровневом языке высокоуровневые абстракции не нужны. NonNull не нужен.
Давайте вы не будете выдавать свои фантазии за мои слова, OK?
UFO just landed and posted this here
UFO just landed and posted this here
приводит в плюсы недостатки проектирования

Ну вот у меня есть микроконтроллер, на нем Сишная библиотека, проект на крестах. Поскольку в эмбеддеде все плохо (нет, не так, все ОЧЕНЬ ПЛОХО) с системным окружением, то каждый лепит свои велосипеды. И на каждый выделяемый в системе объект есть свой Си-шный деструктор. Вместо того, чтобы переписывать хитрозапутанный TCP-шный стек на ржавчину, я просто беру и оборачиваю все эти объекты в более безопасный unique_ptr, чтобы ненароком не забыть освободить динамически выделенную память.

я просто беру и оборачиваю все эти объекты в более безопасный unique_ptr

Угу. Без unique_ptr прям жить нельзя. Можешь почитать про деструкторы. Говорят, это встроено в С++98.

И руками писать move-конструктор? Лень

Писать на цепэпэ98 — это издевательство над личностью.

Пейсать на цепэпэ — это издевательство над личностью.

UFO just landed and posted this here
они периодически вызывают деструкторы, а ты должен молиться всем богам, чтобы у тебя не было живых ссылок на объект в момент вызова деструктора.

Ну если погромист решил погеройствовать и явно дергает сырые указатели из shared_ptr/unique_ptr — то это его проблемы. Суть как раз в том, чтобы не оставлять сырых указателей где-то еще, помимо самой обертки.


Единственная проблема — легаси код, который про unique_ptr, shared_ptr знать не знал.


unique_ptr/shared_ptr ни за что и не отвечают

Они отвечают за RAII поверх указателей. Как только объект у нас выходит за пределы области видимости — его С++ грохает через вызов деструктора. Плюшка С++ в том, что деструктор у этих указателей можно переопределить статически или в рантайме.

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

А теперь медленно перечитываем мое предложение до осознания:


Потому что RAII хорош только в том случае, когда ты используешь так называемые "правила хорошего тона".

Ага, классная суть. При этом у умных указателей в API вовсю наружу висят сырые указатели, типа всяких unique_ptr::get. Они есть, но ты их не трогай, ага.

Они есть, но ты их не трогай, ага.

Именно. Но объявлять устаревшим старое API — это делить на ноль то, ради чего кресты держат обратную совместимость.

Они есть, но ты их не трогай, ага.
Они есть поскольку есть гигатонны старого C++ного и еще дофига разного plain-old-C кода, с которым нужно взаимодействовать. А если в современном коде указатель лежит в unique_ptr/shared_ptr, то как иначе вытащить его наружу для передачи в старый/сторонний код?
Ну и не могу отказать себе в удовольствии. Раз уж вы хотите указать на косяки «контроля» в C++, то давайте сделаем пример еще проще:
int main() {
  int a = 0;
  { std::unique_ptr<int> b(&a); } // Oops!
}

Ну и теперь можно накатать целый трактат на тему отсутствия контроля времени жизни в C++.

Это не проблема отсутствия контроля времени жизни в С++, это проблема отсутствия unsafe. Если бы в C++ добавили unsafe, то можно было бы написать аналог unsafe метода Box::from_raw. Таким образом ты или пишешь unsafe { Box::from_raw(&10); } (что требует от тебя повышенного внимания к коду и проверки того, что &10 является корректным аргументом (иначе ты ССЗБ)), или у тебя не скомпилируется программа.


А целый трактат про unsafe уже написан, встречайте The Rustonimicon.

Это не проблема отсутствия контроля времени жизни в С++, это проблема отсутствия unsafe.
Извините, но вы переворачиваете все с ног на голову. В C++ все небезопасно, это его идеология. Поэтому в C++ все изначально unsafe и, если чего-то в C++ не хватает, так это введения safe-подмножества, а вовсе не выделение отдельного unsafe.

А раз так, то вы C++ вы отвечаете за то, что передаете в unique_ptr — уникальный указатель, не уникальный указатель, указатель на динамически созданный объект или указатель на значение на стеке (что, вообще-то говоря, так же может иметь смысл). Это находится вне зоны ответственности unique_ptr. Как и вне зоны ответственности Box-а в Rust-е.

А вот если сравнивать сами unique_ptr и Box, то чем один лучше другого?
хорошо, что тут еще и не втянули std::weak_ptr, std::auto_ptr (который уже давно deprecated по понятным всем историческим причинам) и, конечно же, boost::intrusive_ptr (своеобразный shared_ptr).
Но всё же, во-первых, умные указатели являются не частью языка, а частью библиотеки C++. Появились они в недрах плюсовиков еще до появления Rust со своей пачкой умных указателей, на каждый случай и со стат. анализатором, как примесь к компилятору.
Но в плюсах то и голому указателю находиться применение. Например, мне не нужно атомарное копирование shared_ptr, мне не нужно перемещение указателя unique_ptr. Окей. На этот случай есть константная ссылка. Но, в тех же аллокаторах может понадобиться какая-нибудь адресная арифметика.
Плюс, назревает появление какого-то std::observer_ptr в копилку…
В Rust из коробки есть то, что появилось со временем в плюсах еще до появления Rust, только с блэкджеком. И что?
Этот весь бесполезный и не логичный спор напоминает мне противостояние винды(Rust) и Linux(C++).
Rust пока еще молодой язык, который не успел нагрешить и не несёт на себе тяжёлый груз обратной совместимости. И неизвестно еще чьи грехи будут страшнее.
вам говорят о том, что из-за правил раста и основанных на этих правилах гарантиях Box/Rc/Arc зачастую не нужны там, где они нужны* в с++. И сравнивают они, соответственно, умные указатели из с++ с обычными ссылками из раста.

*спорный момент. Да, shared_ptr/unique_ptr предоставляют гарантии, которых не дают простые ссылки. Но в однопоточных сценариях используются в основном вторые, потому что проследить за временем жизни объекта несложно.

Единственный аргумент про лучше/хуже — shared_ptr/unique_ptr дольше печатать чем Box/Rc/Arc. Я могу добавить, что первые поддерживают deleter'ы.
И сравнивают они, соответственно, умные указатели из с++ с обычными ссылками из раста.
Это я понимаю. Я не понимаю, почему они так делают.
спорный момент
Вот именно. Более чем спорный, особенно в современном C++, в котором есть move semantic и возврат/передача «по значению» после C++11 заменяет множество случаев, где в старом C++ использовали, в том числе, и динамическую память.
UFO just landed and posted this here
Тред большой, я не осилил
Квинтэссенция здесь.
UFO just landed and posted this here
И мне это как-то потихонечку начинает надоедать.
Это многим уже надоело. Но массовый выход, имхо, вовсе не в виде Rust-а или чего-то сравнимого с ним по сложности, а в языках, вроде Go.

PS. Извините, на остальные комментарии не могу отвечать. Тред уже исчерпал лимиты терпения и толерантности. Если есть желание о чем-то пообщаться предметно, то можно через личку.
UFO just landed and posted this here
в C++, как и в Rust-е, вы не можете возвращать ссылку/указатель на член локального вектора. Только в C++ вам за это по рукам никто не бьет
Вообще-то, бьют по рукам, только не на этапе компиляции, а на этапе статического анализа. Правило F43 Core C++ Guidelines.
не на этапе компиляции, а на этапе статического анализа
Это и есть принципиальное отличие. В Rust-е за счет правил владения разработчик получает от компилятора то, что в C++ нужно брать с помощью внешних инструментов (статических и динамических анализаторов, санитайзеров).
Согласен. Кому важно, что предупреждение выдается компилятором, а не встроенным в среду статическим анализатором, который запускается при каждой сборке, тем имеет смысл перейти с C++ на Rust.
Кстати говоря, современные gcc и clang довольно часто выдают предупреждения, когда из функции пытаешься вернуть ссылку/указатель на временный объект. И, есть надежда, что по мере проработке C++ Core Guidelines и анализаторов для них, эти диагностики будут становиться только лучше.
Вопрос не в этом. В расте тоже есть clippy, в котором есть всякие правила, которых нет в языке, и который можно гонять так же, как Core Guidelines. Вопрос в том, что подобные гайдлайны очень трудно проверять, чтобы с одной стороны найти все ошибки, с другой не выдавать ложноположительных результатов, а с третьей не скатиться в карго-культ, где анализатор форсирует единственно верный формат архитектуры.

Rust тут выступает по сути как язык с ограничениями, которые позволяют упростить анализ и сделать его более надежным и более умным. По сути раст это и есть же в некоторой степени «С++ со статическим анализатором из коробки», в котором по ходу дела поправили самые болевые места. Я тут недавно пересматривал первую презентацию по расту (2010 год!), так там одной из киллер-фич языка было «Nothing new. Using only proven features».
Rust тут выступает по сути как язык с ограничениями, которые позволяют упростить анализ и сделать его более надежным и более умным.
Вот это очень здорово и очень интересно. А какие именно есть проверки, которые статические анализаторы C++ делать не умеют и вряд ли научатся? Я подозреваю, что спрашиваю что-то очевидное из-за недостатка опыта общения с Rust, буду рад, если кинете ссылку.

Например, можно ли модифицировать данные в другом потоке? Можно ли разделить переменную в нескольких потоках на чтение?

Как раз незадолго до вас уважаемый PsyHaSTe наконец смог донести до меня этот пример (ну и долго же я тупил!)
Согласен.

Ну нет. Статически анализ — это уже инструмент, но тут важен сам язык. А разница в языках тут огромна. Rust безопасен с точки зрения memory safety и data races по дефолту. Чтобы знать Rust вам нужно знать правила владения и заимствования встроенные в язык, чтобы ваш код скомпилировался. Соответственно за человека, который знает Rust можно не волноваться, что он будет писать код, который противоречит здравому смыслу (с точки зрения memory safety и data races). C++ же напротив небезопасен по дефолту, какой-нибудь зелёный джун скомпилирует свой код несмотря на то, что он не корректен, а фидбек по этому поводу он получит в лучше случае во время статического анализа на локалхосте, а в худшем — когда у вас что-то упадёт. При этом чтобы сделать его безопасным нужно выставить какие-то флаги компиляции, да ещё и статический анализатор прикрутить к CI. Соответственно к коду человека, который выучил C++ у вас не будет никакого доверия пока вы не удостоверитесь что он выучил стандарт, C++ Core Guidlines и ещё может быть что-то, не знаю что сейчас должен знать каждый уважающий себя разработчик на C++.


Rust банально проще для новичков, а проще он от того, что его компилятор помогает учиться писать корректный код. В связи с этим можно сделать достаточно простой вывод относительно будущего Rust'а и C++: Rust предоставляет во многом те же профиты что и C++, только вот нанять программировать на Rust'е можно какого-нибудь жсера, которых сейчас пруд пруди, или вообще студента без опыта, достаточно быстро научить его писать код на Rust(при этом большую часть работы по обучению сделают растбук с компилятором) и спокойно принимать от него PR'ы без необходимости проводить долгое и тщательное код ревью. С C++ такой трюк, насколько я знаю, не пройдёт, вы либо нанимаете джунов, а потом долго и мучительно их учите прежде чем они начнут делать больше пользы, чем приносить вреда, либо нанимаете команду матёрых C++ программистов с 5+ лет опыта, которым платите 300kk/nanosec.

нанимаете команду матёрых C++ программистов с 5+ лет опыта, которым платите 300kk/nanosec.

Которые тоже могут выдавать код с UB. И их UB гораздо сложнее найти, потому что тривиальные ошибки эти эксперты, как правило, не допускают.

Чтобы знать Rust вам нужно знать правила владения и заимствования встроенные в язык, чтобы ваш код скомпилировался. Соответственно за человека, который знает Rust можно не волноваться, что он будет писать код, который противоречит здравому смыслу (с точки зрения memory safety и data races).
Все же человек обычно программирует не в блокноте. Вам в любом случае потребуются pre-commit hooks для проверки того, что код соответствует style guide и собирается. Добавить туда же проверку, что статический анализ не выдает ошибок, и неожиданно все те же самые рассуждения применимы и к C++.
При этом чтобы сделать его безопасным нужно выставить какие-то флаги компиляции, да ещё и статический анализатор прикрутить к CI.
Так это же хорошо, разве нет? Можно проверять, можно не проверять — это лучше, чем всегда проверять. Ну и ЕМНИП по умолчанию флаги включены, как минимум, в Visual Studio.
к коду человека, который выучил C++ у вас не будет никакого доверия пока вы не удостоверитесь что он выучил стандарт, C++ Core Guidlines и ещё может быть что-то
Core C++ Guidelines отсекает немалую часть дебрей. Выучить только то подмножество C++, которое ограничено Core C++ Guidelines, проще, чем выучить весь стандарт.
Rust банально проще для новичков
Fair point.
либо нанимаете команду матёрых C++ программистов с 5+ лет опыта, которым платите 300kk/nanosec.
Это еще не факт, что минус, для того самого матерого C++ программиста с 5+ лет опыта и высокой зарплатой.
Все же человек обычно программирует не в блокноте. Вам в любом случае потребуются pre-commit hooks для проверки того, что код соответствует style guide и собирается. Добавить туда же проверку, что статический анализ не выдает ошибок, и неожиданно все те же самые рассуждения применимы и к C++.

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

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

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

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


Но да, дискуссия сошлась, просто я как-то перечитыал и решил уточнение дописать.

выделять сейф подмножество очень трудоемко даже для комитета
Мы в эту сторону движемся, с Core C++ Guidelines.
решил уточнение дописать
И за это вам спасибо от лица будущих читателей этой статьи
UFO just landed and posted this here
UFO just landed and posted this here
Пролог — очень красивая идея. Но когда начинаешь писать, упираешься в сложность использования и понимания чисто декларативной семантики и скатываешься в директивную. Видимо для разных задач неизбежны разные языки, несмотря на формальную равномощность.
Для моих целей Rust слишком сырой.Я пишу программы для диагностического медицинского оборудования уже лет 20. С++ старое и хорошо известное зло. Добавив немного кодогенерации на прологе, лиспе или даже питоне можно обойти большинство узких(неудобных) мест С++. Да и мне не надо писать быстро, мне надо — надежно. «Лучше день потерять, потом за пять минут долететь»-«C» :)

Ну как по мне, единственная проблема Rust сейчас — небольшое количество качественных библиотек. Но для медицинского оборудования я как раз бы предпочел раст (при условии, что есть необходимые библиотеки. Если нет, то деваться некуда). Язык молодой, это да, но смотря с чем сравнивать, как говорится.


Я бы предложил в качестве эксперимента попробовать переписать какой-нибудь модуль, сравнить, сколько времени из этого займет непосредственно отладка gdb, ну и сравнить результаты с сишным модулем. Возможно, вы будете приятно удивлены.

Мы ведь обсуждаем нововведения в с++? Нравится Вам раст, пишите на нем на здоровье, а я немного подожду пока устаканится. Ну как Вам объяснить? Ну вот к примеру научные тексты на русском формулируются весьма коряво. Но ценность самой научной работы от языка зависит мало и для смены языка нужно более веские основания чем мода. А раст пока сырой. Вот напишут на нем пару ядер для операционок, соберут компиляторы под микроконтроллеры и GPU :) — вот тогда и поглядим. Пролог тоже в свое время (лет 30 назад) декларировался как убийца процедурных языков (японцы угроХали кучу денег на параллельный пролог) и где он теперь?
А раст пока сырой.

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


Ну и с моей колокольни это смотрится забавно рядом с кодогенерацией (С++) на прологе и лиспе.

пару ядер для операционок

есть


соберут компиляторы под микроконтроллеры

есть


и GPU :)

есть


— вот тогда и поглядим

Глядите)) Ждем отчетов.

Эти тесты уже давно не принято упоминать в приличном обществе :)
Эксперименты вообще вещь неприличная. Мешают таким красивым теоритичиским построениям
Да нет, там дело в другом. Были и вопросы к качеству самих реализаций, и адекватности результатов.

Если вам не важна безопасность, то конечно же не вступайте. Бегите прочь! Иначе ваши программы будут работать стабильно, вы будете проводить раз в 500 меньше времени в отладчике. И зарплата будет раза в 3-4 выше. Бегите! Не создавайте конкуренцию :D

Если у Вас отладка стала занимать в 500 раз меньше времени, значит на с++ Вы писать не умели и могу Вам только посочувствовать. Я и на с++ отладчиком пользуюсь только для чужого кода. Дам подсказку. Правильный дизайн сокращает время отладки до нескольких процентов от времени написания кода. И неважно на каком языке.
значит на с++ Вы писать не умели и могу Вам только посочувствовать. Я и на с++ отладчиком пользуюсь только для чужого кода.

А кто сказал, что ошибки были в моем коде? Откуда столько грубости?


Software architect в вашем лице не умеет играть в командную работу?


Правильный дизайн сокращает время отладки до нескольких процентов от времени написания кода.
На большинстве тестов раст проигрывает, причем значительно, иногда почти вдвое

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

Правильный дизайн сокращает время отладки до нескольких процентов от времени написания кода

Или может Software architect в вашем лице боится потерять работу, которую может выполнить компилятор?

И вообще, человек с должностью Software architect должен слышать про цикломатическую сложность, а не городить for/if/for/if/for/if.


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

вы будете проводить раз в 500 меньше

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

Больше доверяйте компилятору, повнимательней гляньте на ассемблер, во что это компилируется :). Он умеет разворачивать константные циклы и вытаскивать инварианты из цикла. Читается понятно, а на скорость не влияет. Кроме того, это быстенько накиданный демо бенчмарк к статье, частью сгенерированный. Да и хороший дизайн это работа. Которая стоит денег.

Аргументы «сперва добейся» стоит раздавать

Где? Вы меня с кем то спутали…
Больше доверяйте компилятору, повнимательней гляньте на ассемблер, во что это компилируется :)

Да вы, батенька, не знаете про цикломатическую сложность. Она про сложность работы программиста, а не про скорость работы программы.

Вам известен способ обхода соседей в многомерном массиве короче чем вложенный for / if / for if /for в нескольких строчках кода? Поделитесь :). Для меня код достаточно прозрачный, ключевое слово — «достаточно».
Впрочем я не спорю на религиозные темы с адептами религии и ревнителями чистоты кода. Поэтому заканчиваю дискуссию. Удачи Вам с RUST-ом…
Вам известен способ обхода соседей в многомерном массиве короче чем вложенный for / if / for if /for в нескольких строчках кода? Поделитесь
const array<array<int, 2>, 4> deltas {
        0, 0,
        0, 1,
        1, 0,
        1, 1};
for (const auto& delta : deltas) {
    auto x = baseX + delta[0];
    auto y = baseY + delta[1];
    // Делаем что нужно с элементом [x][y]
}
А кто будет границы отсекать?
const array<array<int, 2>, 4> deltas {
        0, 0,
        0, 1,
        1, 0,
        1, 1};
for (const auto& delta : deltas) {
    auto x = baseX + delta[0];
    auto y = baseY + delta[1];
    if (out_of_bounds(x, y, size1, size2))
        continue;
    // Делаем что нужно с элементом [x][y]
}
Да и код должен бежать на ANSI C++ / ISO-C++
Вы какой язык имеете в виду? Я не знаю стандарта ANSI C++, только ANSI C, но то совсем другой язык. Реализация выше соответствует стандарту ISO/IEC 14882:2011 (вышедшему более 6 лет назад) и последующим.

Вот реализация той же идеи на C++98, первом стандарте ISO.
const int deltas_num = 4;
const int deltas[deltas_num][2] = {
        {0, 0},
        {0, 1},
        {1, 0},
        {1, 1}};
for (int i = 0; i < deltas_num; ++i) {
    int x = baseX + deltas[i][0];
    int y = baseY + deltas[i][1];
    if (out_of_bounds(x, y, size1, size2))
        continue;
    // Делаем что нужно с элементом [x][y]
}
UFO just landed and posted this here
Для проверки на границы? Ну мало ли, там что-то более сложное.
UFO just landed and posted this here
    if (out_of_bounds(x, y, size1, size2))
        continue;

Да, именно так и уменьшается цикломатическая сложность. А вот засовывать for под if, да еще и множество раз — зло.

По-моему, тут даже другая идея важнее — можно for сделать один вместо нескольких. Пока их два — не так страшно, но бывает ведь и больше. Да и топология бывает сложнее 4-связности.
UFO just landed and posted this here
имо лучше ограничивать диапазоны, чем проверять выход за них. В с++17 даже std::clamp завезли для подобного
clamp лучше. Но данном конкретном случае не подходит. Это поиск близлежащих точек в пределах некоторого радиуса. Тестовая точка может упасть за краем распределения или на краю. Я прекрасно знаю что я делаю в этой моей программе, которую внезапно взялись тут обсуждать.
Вы такой код положите в продукт? А для трехмерной матрицы и с обходом не две соседних ячейки, а в 4?
Сравните:
//const int RN = 1;
const int RN = 2;
for(int z=1-RN;z<RN+1;z++)
{
  int zz = z+z_offs;
  if(check_bound(zz,0,Z))
  {
    for(int y=1-RN;z<RN+1;y++)
    {
      int yy = y+y_offs;
      if(check_bound(yy,0,Y))
      {
        for(int x=1-RN;z<RN+1;x++)
        {
          int xx = x+x_offs;
          if(check_bound(xx,0,X))
          {
              //	do something with a[zz][yy][xx];
          }
        }
      }
    }
  }
}


Паттерн легко узнаваемый и легко модифицируется.

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

Я все же рекомендую почитать про цикломатическую сложность. Вот минимальный пример, как можно улучшить код:


//const int RN = 1;
const int RN = 2;
for(int z=1-RN;z<RN+1;z++)
{
  int zz = z+z_offs;
  if(!check_bound(zz,0,Z))
  {
    continue;
  }

  for(int y=1-RN;z<RN+1;y++)
  {
    int yy = y+y_offs;
    if(!check_bound(yy,0,Y))
    {
      continue;
    }

    for(int x=1-RN;z<RN+1;x++)
    {
      int xx = x+x_offs;
      if(!check_bound(xx,0,X))
      {
        continue;
      }

      //    do something with a[zz][yy][xx];
    } // end for x
  } // end for y
} // end for z

А с do нотацией или list comprehensions это вообще можно упростить в ноль.

Это вопрос вкуса. Я избегаю continue в коде когда выход далеко. Это вариант goto.
Это не вопрос вкуса, это вопрос того, насколько легко воспринимать код. Вот, почитайте на досуге, тут довольно неплохо все расписано.
x и у конечно
//const int RN = 1;
const int RN = 2;
for(int z=1-RN;z<RN+1;z++)
{
  int zz = z+z_offs;
  if(check_bound(zz,0,Z))
  {
    for(int y=1-RN;y<RN+1;y++)
    {
      int yy = y+y_offs;
      if(check_bound(yy,0,Y))
      {
        for(int x=1-RN;x<RN+1;x++)
        {
          int xx = x+x_offs;
          if(check_bound(xx,0,X))
          {
              //	do something with a[zz][yy][xx];
          }
        }
      }
    }
  }
}

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

Ваш вариант мне лично неудобно читать, он слишком сфокусирован на низкоуровневых деталях. Моя цель — пробежаться по соседям конкретной ячейки, а не пробежаться по по первому измерению массива, внутри по второму, внутри по третьему, проверки сделать, и так далее. В моем варианте код явно выражает намерения разработчика: сначала генерация топологии, потом использование этой топологии для того, чтобы пробежаться по соседям. Причем не по всем, а только по существующим.
А для трехмерной матрицы и с обходом не две соседних ячейки, а в 4?
Вот с произвольным размером, для трехмерной матрицы:
Заголовок спойлера
vector<valarray<int>> generate_3d_topology(int window) {
    vector<valarray<int>> ans;
    for (int i = -window; i <= window; ++i) {
        for (int j = -window; j <= window; ++j) {
            for (int k = -window; k <= window; ++k) {
                ans.push_back({i, j, k});
            }
        }
    }
    return ans;
}

valarray<int> get_3d_indices(const valarray<int>& base, const valarray<int>& shift) {
    return base + shift;
}

bool indices_in_range(const valarray<int>& indices, const valarray<int>& sizes) {
    for (size_t i = 0; i < indices.size(); ++i) {
        if (indices[i] < 0 || indices[i] >= sizes[i]) {
            return false;
        }
    }
    return true;
}

// Для тех, кто привык индексы в отдельных переменных держать
tuple<int, int, int> unpack_indices(const valarray<int>& indices) {
    return{indices[0], indices[1], indices[2]};
}

template<typename Lambda>
void iterate_3d_neighbors(const valarray<int>& base, int window, const valarray<int>& sizes, Lambda lambda) {
    auto topology = generate_3d_topology(window);
    for (const auto& neighbor : topology) {
        auto neighbor_indices = get_3d_indices(base, neighbor);
        if (!indices_in_range(neighbor_indices, sizes)) {
            continue;
        }
        auto [x, y, z] = unpack_indices(neighbor_indices);
        lambda(x, y, z);
    }
}

...

// Пример использования:
iterate_3d_neighbors(
        {1, 5, 7},
        4,
        {10, 10, 10},
        [v](int x, int y, int z) {
            cout << x << ' ' << y << ' ' << z << ' ' << a[x][y][z] << endl;
        });
Получается пяток функций, каждая из которых совершенно очевидна из-за размера в пару строчек и четко определенной задачи. Место использования свободно от лишней для него логики итерирования по всем размерностям и проверок на допустимость комбинации параметров. Кстати, если мы заранее знаем, что будем итерироваться по контейнеру, можно еще упростить код, не передавая размер, а передавая сам контейнер, да и в лямбде захватывать не придется.
А вот в вашем варианте вся логика — генерация комбинаций индексов, проверка их на валидность, собственно, обработка элемента — свалена в одну большую кучу. Да и отказ от использования continue приводит к дикому количеству отступов. Добавляя к этому переменные с непонятными именами (в чем разница между x, xx и X?) получаем код, который человеку вне контекста не понятен.

Кстати, тот же самый подход без проблем обобщается на случай произвольной размерности и на разреженную топологию. А вот как вы своими циклами переберете, например, все ходы шахматного коня?
PS: Жаль, конечно, что Хабр сжал код до 55 символов в ширину, в реальной работе у нас такого жесткого ограничения обычно нет. IdeOne
to alexeykuzmin0
Ваш код провалится по производительности от 2 до 10 раз, в сравнении с моим.И это в критическом внутреннем loop-е, из-за замены констант на переменные. Не говоря уже о читаемости. Не стоит усложнять абстракциями простой код, написанный за несколько минут и работающий уже лет 10. Вы хоть бы посмотрели в каком контексте он работает.
все ходы шахматного коня

смешно.
Это не библиотека, это код выполняющий вполне определенную функцию в определенном месте, а не сферический конь в вакууме. В другом месте кода обход выполнен именно в виде развернутой таблицы.Да и не стоит учить меня программировать. Я этим занимаюсь с 1978 года.
Ваш код провалится по производительности от 2 до 10 раз, в сравнении с моим
Да, этот код медленнее, но более общий. Первый приведенный мной код (после замены массива на массив из 8 элементов для трехмерного случая) на моем компьютере работает в полтора раза быстрее вашего (1.1 с на 100 млн итераций против 1.8 с).
Не говоря уже о читаемости.
На мой взгляд, если мы разбиваем простыню с переменными x, xx и X и семью уровнями отступов на несколько функций в пару строчек с очевидным назначением, читаемость повышается. Вы можете считать иначе, если хотите.

Вы хоть бы посмотрели в каком контексте он работает.
Я не говорю, что код чем-то плох или неуместен — для этого мне недостает контекста, как вы и сказали. Как вы можете заметить, эта ветка началась с моего ответа на фразу
Вам известен способ обхода соседей в многомерном массиве короче чем вложенный for / if / for if /for в нескольких строчках кода? Поделитесь
Мне такой способ известен, я им с вами поделился, как вы и просили.
Ну а у меня Ваш код проигрывает для 10^6 тестов
g++ -O3 -std=c++11 -march=native -msse4.1 -funroll-loops test.cpp
const int size1 = 4;
const int size2 = 6;	

inline bool out_of_bound(int x,int size)
{
	return !(x>=0&&x<size);	
}
inline bool check_bound(int x,int size)
{
	return (x>=0&&x<size);	
}
inline bool out_of_bounds(int i1,int i2,int size1,int size2)
{
	return !(check_bound(i1,size1)&&check_bound(i2,size2));	
}

const int RN = 4;
const int deltas_num = RN*2*RN*2;
int	deltas[deltas_num][2] ; 
		
void init_deltas()
{
	int i = 0;
	for(int y=1-RN;y<RN+1;y++)
	{
		for(int x=1-RN;x<RN+1;x++)
		{
			deltas[i][0] = x;
			deltas[i][1] = y;
			i++;
		}
	}
}
inline int     test_w_deltas(int baseX,int baseY)
{
	int count = 0;
	for (int i = 0; i < deltas_num; ++i) {
	    int x = baseX + deltas[i][0];
	    int y = baseY + deltas[i][1];
	    if (out_of_bounds(x, y, size1, size2))
		continue;
	    count ++;
	}
	return count;
}

inline int     test_w_if(int baseX,int baseY)
{
	    int count = 0;
	    for(int y=1-RN;y<RN+1;y++)
	    {
	      int yy = y+baseY;
	      if(check_bound(yy,size2))
	      {
		for(int x=1-RN;x<RN+1;x++)
		{
		  int xx = x+baseX;
		  if(check_bound(xx,size1))
		  {
		      //	do something with a[zz][yy][xx];
			  count++;
		  }
		}
	      }
	    }
	    return count;
  }
...
main()
{
std::vector< std::pair<int,int> > testVec;  

	std::random_device rd;    
	std::uniform_int_distribution<int> uni(-20,20);
  
	for(int i=0;i<1000000;i++)
	{
		testVec.push_back(std::pair<int,int>(uni(rd),uni(rd)));
	}	
	init_deltas();
	int  count = 0;
	{
		HiCl cl("test_w_deltas");
		for (const auto& test : testVec)
		{
			count+=test_w_deltas(test.first,test.second);
		}
	}
	PRINT(count);
	count = 0;
	{
		
		HiCl cl("test_w_if");
		for (const auto& test : testVec)
		{
			count+=test_w_if(test.first,test.second);
		}
	}
	PRINT(count);
	count = 0;
	{
		
		HiCl cl("test_w_deltas");
		for (const auto& test : testVec)
		{
			count+=test_w_deltas(test.first,test.second);
		}
	}
	PRINT(count);
	count = 0;
	{
		
		HiCl cl("test_w_if");
		for (const auto& test : testVec)
		{
			count+=test_w_if(test.first,test.second);
		}
	}
	PRINT(count);
	return 0;
}

N=2 RN=1

test_w_deltas 3.053 ms
57704 count
test_w_if 2.685 ms
57704 count
test_w_deltas 3.292 ms
57704 count
test_w_if 2.528 ms
57704 count
RN=2
test_w_deltas 9.86 ms
228680 count
test_w_if 4.355 ms
228680 count
test_w_deltas 10.054 ms
228680 count
test_w_if 4.649 ms
228680 count

RN=3
test_w_deltas 24.202 ms
513030 count
test_w_if 11.056 ms
513030 count
test_w_deltas 24.88 ms
513030 count
test_w_if 11.065 ms
513030 count
Как я и предполагал — до двух раз.
Я делаю вот так, с теми же ключами (точнее, сначала я запустил с другими, но сейчас перезапустил с вашими, результаты не поменялись), результат — от 1.8 до 1.9 с у первого фрагмента кода и от 1.12 до 1.19 с у второго. У меня внутри используется более сложная операция, чтобы оптимизатор не смог провести слишком умную оптимизацию и склеить множество итераций в одну короткую формулу (например, несколько ++ заменить на прибавление числа итераций цикла), потому что в реальном коде мы не делаем циклов с телом, состоящим из одного ++.

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

Единственное, что я хотел сказать этой веткой — ваш вариант не единственный. Вы просили пример другого кода, решающего ту же задачу — я его привел.
Посмотрите с чего началась история. Она началась с претензий к моему коду. Что написано плохо.И что писать я не умею.А в ветку Вы попали с середины. ( habr.com/post/415737/#comment_18836393 )
Код был написан очень давно, скопипастен в демку и я не видел и не вижу необходимости его менять. Задавая вопрос можно ли его переписать, я очевидно имел в виду «можно ли его переписать с сохранением скорости, внятности и возможности расширения-модификации» что было не-очевидно в моем посте. Да и по прежнему считаю, что он лучше, проще и понятнее. Впрочем это уже вкусовшина
ОК, чтобы это было четко и ясно: я не считаю, что вы писать не умеете. Мысль, которую я хотел привнести в эту ветку, заключается в существовании альтернативного метода итерирования по соседям в многомерном массиве.
ОК. Вы понимаете, это все безусловно верно и пример правильный, просто немного получилось не к месту. Если рассуждать абстрактно, то откладывание проверки до внутреннего цикла может обойтись очень дорого. Хотя в данном случае — это неважно.
Действительно, получилось не к месту. Пожалуй, мне стоило в первом же комментарии указать, что я имею в виду лишь существование подобного подхода, ничего больше.
Уважаемый alexeykuzmin0, конкретно эта часть работает в коде реального времени регистрации видео изображений.
Это из сегодняшней статьи, почитайте.
habr.com/post/416167
Не забывайте, что в словосочетании «компьютерные науки» есть слово компьютер. Запомните, сколько времени требуется вашему компьютеру на выполнение одной инструкции, на чтение машинного слова из памяти (с и без промаха в кэше), на чтение последовательности машинных слов с диска, поиска записи на диске (Ответы здесь)
Уважаемый alexeykuzmin0 показал код, который читаемее и работает быстрее (по крайней мере, на его железе). С чего вы вдруг решили, что ваш вариант лучше или быстрее?
И зарплата будет раза в 3-4 выше
А разве зарплата на Rust в 3-4 раза выше, чем на C++?

Вообще вакансии Rust довольно денежные.

Я верю, что денежные, но настолько? Только что посмотрел на hh — в Москве самая денежная зарплата на Rust предлагает на 5-7% меньше денег, чем было у меня в Москве на C++ на момент отъезда. Я, конечно, допускаю, что вакансии, где зарплата не указана, могут предлагать немного больше, но втрое-вчетверо?
а вот SO developer survey 2018 с вами не согласен. Да, при примерно одинаковом опыте rust предлагает на 40% больше (не в 3-4 раза) плюсов. Интересно почему. Полагаю это из-за того, что плюсам учат студентов, в то время как rust-разработчики по большей части выходцы из других яп.
почти никто не приходит в раст первым языком, чаще после нескольких лет опыта в чем-то другом. Я думаю, в этом и причина.
Наверное, потому что Rust-разработчика с 5-летним опытом найти несколько труднее, чем плюсовика с тем же стажем :)
вас послушать, так разраб на расте с годом опыта напишет больше и лучше чем разраб с++ с пятилетним стажем…
Я такого не говорил. Я сказал только то, что раст — молодой язык, и найти разработчика с определенным стажем на нем сильно сложнее. Делать такое сравнение — неблагодарное занятие гадания на кофейной гуще.
у вас в этом треде коментов 50 с посылом «раст надежнее, раст производительнее, раст проще и в целом лучше». Наверно вы очень тонко на это намекнули. При этом мне сложно верится, что rust-разработчики прям так уж нарасхват: на hh в 70 раз меньше вакансий на rust чем на плюсах и в 2/3 из них указан с/с++/rust. Полагаю, вопрос скорее в хайповости — то, на что рискнут в кремниевой долине (где зп естественно выше), не рискнут у нас
Наверно вы очень тонко на это намекнули.

Если я прямо говорю, что я на это не намекал, то возможно это еще более глубокий намек… Хотя может я просто говорю, что думаю?
При этом мне сложно верится, что rust-разработчики прям так уж нарасхват: на hh в 70 раз меньше вакансий на rust чем на плюсах и в 2/3 из них указан с/с++/rust.

Если вакансий на раста в 70 раз меньше, чем плюсовых, а разработчиков на расте в среднем 1 на 1000 плюсовиков, то можно ожидать спокойно подобного роста зарплаты, разве нет? Не потому, что спрос такой высокий или это сверхлюди, а просто потому, что на рынке сейчас низкое предложение.

Прошел год. На большинстве бенчей языки идут вровень, есть один бенч, где раст выигрывает почти вдвое, есть один тест, где С++ выигрывает почти вдвое. Записывайтесь в секту))

Прошло 1.5 года. Раст выигрывает 6 пунктов из десяти. Самое время записаться в секту? Видимо нет)))


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

UFO just landed and posted this here

Первый пример с визитором и printf это конечно отдельный перл (в статье про c++17)…
Что помешало сделать так?


std::visit([](auto&& arg){std::cout << arg;}, variant_instance);

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


P.S.: Статья о том как не осилил constexpr и std::visit.

Да ладно, так уж и писец. У всего своя область применения. А если автор статьи не осилил в совокупности, то что ему упростит работу, а выдрал кусочек и привязал его к С++11 или тем более к С, то это не вина языка, а вина неосилятора. Конечно на интерпретируемых языках или языках с динамической компиляцией ака JIT всё это делается гораздо проще, но они за это тоже платят (например сборкой мусора и джиттером во время сборки).

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

Вобщем неважно в чём причина, в грузе-ли лет, или в недостатках архитекутры. Речь то была о том что «писец» или не «писец». Я как пользователь С++, утверждаю, что никакой ни писец, а вся проблема в том, что автор не осилил нововведения и за уши их притягивает в старый стандарт.

Вы как пользователь C++ предвзяты и не можете объективно оценивать реальность, точно так же как пользователь Swift выше, или как я, пользователь Rust. Для меня и valeriyvan это пипец, для вас нет, это всего лишь субъективные мнения, не стоит из-за этого начинать вешать ярлыки неосиляторов направо и налево.


Однако отсутствие типов сумм как объектов первого класса в C++ это объективный факт, с которым спорить нельзя.

Вы как пользователь C++ предвзяты и не можете объективно оценивать реальность, точно так же как пользователь Swift выше, или как я, пользователь Rust.
Странное заявление. Пользователь С++ оценивает положение дел в C++. Какое отношение к этому имеют пользователи Swift-а или Rust-а?
Однако отсутствие типов сумм как объектов первого класса в C++ это объективный факт, с которым спорить нельзя.
Ну да, их в C++ нет. И что делать C++никам? Менять свой инструмент на какой-то другой или учиться таки пользоваться тем, что в языке есть?
Странное заявление. Пользователь С++ оценивает положение дел в C++. Какое отношение к этому имеют пользователи Swift-а или Rust-а?

Странный вопрос, вы регулярно даёте личную оценку Rust'у в личном блоге, да и не только. С чего бы вдруг мы не могли давать личную оценку C++?


Ну да, их в C++ нет. И что делать C++никам? Менять свой инструмент на какой-то другой или учиться таки пользоваться тем, что в языке есть?

С++ники могут делать что хотят, автор, например, отрицательно отзывается о языке, кто-то старается в своей работе задействовать тот же Rust/Swift/<LANGNAME>, а кому-то удобнее продолжать использовать C++ и не искать в нём недостатки. Что делать вам — ваше личное дело.

Странный вопрос, вы регулярно даёте личную оценку Rust'у в личном блоге, да и не только.
Вопрос не странный, вы просто не поняли его.
С чего бы вдруг мы не могли давать личную оценку C++?
Да это сколько угодно. Но когда C++ник оценивает возможности и особенности C++, то странно обвинять его в предвзятости. Особенно на фоне личного мнения пользователей других языков программирования.
Что делать вам — ваше личное дело.
Если комментарий вида «Однако отсутствие типов сумм как объектов первого класса в C++ это объективный факт, с которым спорить нельзя.» нужен только для того, чтобы высказать свое личное мнение, то это одно. Но это ничем не отличается от надписи на заборе «Вася — дурак».

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

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


Если комментарий вида «Однако отсутствие типов сумм как объектов первого класса в C++ это объективный факт, с которым спорить нельзя.» нужен только для того, чтобы высказать свое личное мнение, то это одно. Но это ничем не отличается от надписи на заборе «Вася — дурак».

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

Я бы мог начать говорить о том, какой Rust хороший, какая в нём прекрасная система типов, как облегчает жизнь borrow checker и как легко его можно "вырубить" там, где он лишь мешает. Вы в ответ начнёте мне доказывать обратное, будете говорить о том, какой в Rust шумный синтаксис, о том, что он чрезмерно сложный и не подходит для решения реальных задач. Вот только мы это всё уже проходили, я все ваши аргументы знаю и со многими не согласен. Вы так же знаете все мои аргументы и со многими так же не согласны. В итоге наш спор ни к чему не приведёт и каждый из нас лишь выразит своё личное мнение и при нём же и останется. Ситуация патовая с самого начала, так зачем играть?

Странно обвинять ...
Вы упорно не понимаете о чем вам говорят.
Я бы мог начать говорить о том, какой Rust хороший, какая в нём прекрасная система типов, как облегчает жизнь borrow checker и как легко его можно «вырубить» там, где он лишь мешает.
И это очередное продолжение того, что вы не понимаете о чем речь.

Статья об одной из особенностей современного C++. Да, вот в C++ сейчас так. Жить с этим нужно только C++никам. Среди которых есть разные мнения на этот счет. И эти мнения имеют практическое значение.

А вот персонажи, которые приходят сказать, что в Swift что-то лучше, чем в C++. Или что в Rust-е что-то лучше. Или что еще где-то что-то лучше. Или что в C++ чего-то нет… Вот эти персонажи напоминают тех, кто хочет просто написать на заборе свое веское мнение. И которым, по большому счету, пофиг на тех, чей это забор.
И которым, по большому счету, пофиг на тех, чей это забор.

Дык, именно этот забор — "общественный". (:

UFO just landed and posted this here
Ну да, их в C++ нет. И что делать C++никам? Менять свой инструмент на какой-то другой или учиться таки пользоваться тем, что в языке есть?


Мое мнение, что стоит менять инструмент. Выгодно если думать на перспективу. Напомню цитату из «побеждая посредственность»:

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


Изучение инструмента, которое повышает продуктивность — всегда полезно.
Мое мнение, что стоит менять инструмент.
Предсказуемо. Допустим, сменить инструмент слишком дорого. Что тогда?
Изучение инструмента, которое повышает продуктивность — всегда полезно.
А изрекать банальности — это как?

Давайте оставим за скобками тезис о том, повышает ли продуктивность Rust, и посмотрим вот с какой стороны: вы отвечаете за большую кодовую базу на C++. Изучили Rust и осознали, что Rust повышает продуктивность. И? Переведете кодовую базу в режим сопровождения и начнете писать новый код на Rust?
Давайте оставим за скобками тезис о том, повышает ли продуктивность Rust, и посмотрим вот с какой стороны: вы отвечаете за большую кодовую базу на C++. Изучили Rust и осознали, что Rust повышает продуктивность. И? Переведете кодовую базу в режим сопровождения и начнете писать новый код на Rust?

Зависит от моих целей. С текущими приоритетами я уйду из компании, где у меня нет возможности пользоваться удобными инструментами.
Тогда все просто: если вас не устраивает C++, вы меняете работу и проблемы C++ вас больше не волнуют. Ну а тем, кто по тем или иным причинам остается с кодовыми базами на C++, приходится использовать то, что есть. И мнение не-C++ников по поводу С++ просто игнорируется, как не приносящее практической пользы.
И? Переведете кодовую базу в режим сопровождения и начнете писать новый код на Rust?

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

Собственно, почему бы и нет? )
Потому что шансы сделать это и выиграть обратно пропорциональны размеру кодовой базы. Если вы отвечаете за проект в 50KLOC, который еще и неактивно развивается, то почему бы и нет?

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

Попытки совместить разработку и на C++ и на чем-то новом будут требовать массу усилий, расширения команд, увеличение бюджетов и т.д., и т.п. Хорошо, если у вас проект разбит на модули, которые взаимодействуют друг с другом через IPC механизмы или через БД. Тогда можно новые модули писать на Rust, старые развивать на C++. Но это вряд ли характерно для ниш, в которых C++ еще живет, имхо.
Если же размеры кодовой базы превышает несколько сотен KLOC и проект постоянно дорабатывается и обрастает новыми фичами, то кардинальная смена языка реализации — это большие шансы угробить проект.

То есть мозилла потратила тонну времени и денег чтобы угробить собственный проект (который несколько больше 50 KLOC)?
То есть мозилла потратила тонну времени и денег чтобы угробить собственный проект (который несколько больше 50 KLOC)?
История с Mozilla, Firefox и попытками заменить кодовую базу Firefox-а с C++ на Rust как раз наглядно показывает, насколько все это трудоемко и долго. И, что особенно важно, многие организации, занимающиеся коммерческой разработкой софта, находятся далеко не в таких мягких условиях, как Mozilla, а работают на более конкурентных рынках.

ЗЫ. На вопросы-то найдите силы ответить, не сливайтесь. А то вы оставляете впечатление человека, который не понимает, о чем говорит.
История с Mozilla, Firefox и попытками заменить кодовую базу Firefox-а с C++ на Rust как раз наглядно показывает, насколько все это трудоемко и долго.

Да, и несмотря на это они этим занимаются. Наверное, им никто не сказал, что это бесполезная трата времени!

ЗЫ. На вопросы-то найдите силы ответить, не сливайтесь. А то вы оставляете впечатление человека, который не понимает, о чем говорит.

Я не собираюсь отвечать на вопросы «бот ли я».
Да, и несмотря на это они этим занимаются. Наверное, им никто не сказал, что это бесполезная трата времени!
Вы, кажется, не умеете читать. А именно, вы не увидели следующее:

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

«Попытки совместить разработку и на C++ и на чем-то новом будут требовать массу усилий, расширения команд, увеличение бюджетов и т.д., и т.п.» — а вот это как раз и можно наблюдать.

Я не собираюсь отвечать на вопросы «бот ли я».
Тогда хотя бы расшифруйте свой пассаж про семантику unique_ptr/shared_ptr.
«Попытки совместить разработку и на C++ и на чем-то новом будут требовать массу усилий, расширения команд, увеличение бюджетов и т.д., и т.п.» — а вот это как раз и можно наблюдать.

Немного неуместно в отношении Mozilla сравнение, ибо не просто взяли новую модную технологию и переписывают с её использованием Firefox. Они сами создали эту технологию, и начали с её использованием писать прототип нового браузерного движка ещё до стабилизации Rust. Всё же у иных компаний, которые решат взять Rust риски будут определённо меньше, чем те, что позволила себе Mozilla.

Всё же у иных компаний, которые решат взять Rust риски будут определённо меньше, чем те, что позволила себе Mozilla.
Главное верить. Ведь мы-то точно не такие как все, у нас все получится.
UFO just landed and posted this here

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

UFO just landed and posted this here
Ну значит они будут постепенно вытеснены с рынка программистами, которые будут более продуктивны с более удобными инструментами. Как по мне, основная работа программиста в том, чтобы постоянно осваивать новые инструменты. Не трюки, нет, их потом разбирать никто не захочет, а именно инструменты, которые повышают скорость и качество разработки без ущерба. И если человек просто ходит на одну и ту же работе 15-20 лет, и учит что-то только «когда совсем припрёт», то видимо он изначально шел не потому, что ему это нравилось, а потому где-то услышал, что на этом денег можно сколотить.

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

Я сам шарпист, и я изучаю раст, потому что он мне кажется проще и понятнее шарпов. Да, синтаксис изначально пугает, да, борручекер вначале не дает натворить глупостей и ругается, но все это изучается, причем изучается быстро. Мне хватило буквально пары месяцев относительно интенсивного написания кода, чтобы начать контрибутить в такие сложные проекты, как например Parity (и я не про исправления в readme, а например перенос кода на последний компилятор). И это мне, человеку, который пришел из языка с GC! Плюсовик вообще наверное за выходные может спокойно освоить.

Мне кажется, что хороший программист всегда старается автоматизировать то, что нужно делать больше одного раза, не говоря уже про то, чем он занимается ежедневно всю рабочую карьеру. Это банально с одной стороны интересно, а с другой очень жалко потерянного времени, когда ты знаешь, что робот справился бы лучше.
Но ведь в С++ std::variant (представляющий тип сумму точно также, как std::function представляет функцию) — это как раз объект первого класса…

Вот когда вариантом можно будет пользоваться без стандартной библиотеки, вот тогда это будет сущность встроенная в язык. Всё же объект первого класса — термин, который имеет несколько другое значение, отличное от того, как я его использовал. За это приношу извинения.

UFO just landed and posted this here
Судя по всему, — да. Раширяемость языка с помощью тьюринг полного языка шаблонов на этапе компиляции «растистов» не устраивает и префикс std:: их коробит (я бы даже сказал «ржавит»). Мне вот всё равно в DSL это реализуется или в самом языке, — результат один.
UFO just landed and posted this here
Если бы алгебраические типы данных и паттерн-матчинг были поддержаны на уровне языка, а не стандартной библиотеки, то мы могли бы иметь в языке более продвинутый switch, в котором можно было бы делать и декомпозицию значений (по аналогии с тем, что делает structured binding в C++17), и использовать гарды (условия в отдельных case), и имели бы контроль со стороны компилятора за полнотой вариантов в switch (как это сейчас делается для switch-ей по enum class-ам). Так что с точки зрения теории, поддержка АлгТД+паттерн-матчинга на уровне компилятора лучше оной в stdlib.

Поэтому если чей-то интерес состоит в том, что бы в какой-то абстрактной табличке сравнения фич языков программирования поставить в строке «паттерн-матчинг» прочерк для C++ и галочку для Rust-а, то тогда претензии к C++ понятны.

Но если смотреть на происходящее с точки зрения пользователей C++, то ситуация становится совсем другой. До C++17 std::variant и std::visit в С++ вообще не было. Если кто-то хотел подобной функциональности, то ему нужно было либо делать свои велосипеды, либо подтаскивать в свой проект сторонние зависимости. А сейчас это все доступно из коробки. Так что с точки зрения действующего C++ программиста, std::variant/std::visit — это сильно много лучше, чем ничего. И претензии по поводу того, что sum-types в C++ добавились не через расширение компилятора, а через расширение stdlib выглядят достаточно абсурдно.
UFO just landed and posted this here
У меня такое впечатление, что все языки программирования активно занимаются заимствованием. Теперь С++ нельзя назвать языком со строгой типизацией. Эдак мы до обычных человеческих языков скатимся с их неоднозначностью…

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

Не хочется Вас расстраивать, но С++ никогда не был языком со строгой типизацией :). Со статической да, но не строгой.
Мы начали с простой цели: посмотреть на содержимое сигма-типа.

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


  • std::size_t std::variant::index(), который вернёт номер варианта.
  • bool std::holds_alternative<T>(), который проверит, верно ли, что внутри лежит T.
  • std::get<T>() и std::get<std::size_t>(), которые позволяют получить значение по типу или номеру.
UFO just landed and posted this here
в отличие от типов-сумм в других языках (и std::any), std::variant не является в полной мере type-erased. Это нужно для универсальности, zero overhead и вычислений в constexpr контексте. Подразумевается, что под конкретную задачу его можно сравнительно легко обернуть.

Ну а про существование более простых методов «обхода» std::variant уже писали.
UFO just landed and posted this here
На работе пришлось последний месяц-два писать на Rust, и после этого C++ кажется жутко неудобным и неотёсанным поделием. Отсутствие типов-сумм внезапно стало очень сильно напрягать, а семантика перемещения в C++ кажется какой-то жалкой пародией и костылем — нет никакой помощи от компилятора для отслеживания использования перемещенных объектов. Шаблоны с утиной типизацией и отвратительными сообщениями об ошибках кажутся каким-то безумием по сравнению с системой типажей (trait).

Раньше я очень любил C++ несмотря на множество его недостатков, но буквально месяц погружения в Rust перевернул мое восприятие с ног на голову. Меня поразило, как часто код на Rust работает правильно с первого раза, и как просто на нем писать асихронные и многопоточные приложения.
Можно поинтересоваться, какой именно C++ вы могли использовать на работе? С++17? С++14? C++11? Что-то более древнее?
C++14. Активно обсуждается переход на C++17, но есть определенные политические препятствия.
Рискну предположить, что вы сравниваете C++14 с современным Rust-ом, а не Rust-ом образца 2014-го года.
Это так, но ради справедливости прошу заметить, что упомянутые вещи практически не поменялись в С++17.
Во-первых, сам Rust за это время поменялся. Не думаю, что программирование на Rust-е до версии 1.0 доставило бы много удовольствия.

Во-вторых, в C++17 язык поменялся не принципиально, но значительно. [[nodiscard]], if constexpr, structured binding и еще несколько вещей сделали программирование на C++17 гораздо более простым и удобным занятием. Да и касательно темы статьи, трюк с visit и overloaded стал возможен именно благодаря изменениям в C++17.
UFO just landed and posted this here
Я не говорю, что Rust обязательно лучше C++ и на нем проще писать во всех контекстах. Просто поделился первыми личными впечатлениями об эргономике языка. Заметьте, что я в своем комментарии обильно использовал слово «кажется», чтобы подчеркнуть, что я делюсь собственным восприятием и не претендую на объективность.

Считаю ненужным что-то вам доказывать. Про опыт и «покажи проекты» — очень толсто, прямо классический пример «сперва добейся». Если вы считаете мое мнение не заслуживающим внимания, вы можете его просто молча игнорировать.
UFO just landed and posted this here
Сколько вы до этого писали на языке С++ и какой версии? Есть у вас опыт комерц использования его хотя бы лет 10?

Миллионы мух не могут ошибаться!

Могут. Но при этом они наговнокодили уже столько готовых к использованию инструментов, сколько в Rust-е не появится в ближайшие N лет.

Поскольку софт пишется не с нуля, а с переиспользованием того, что уже есть, то данный фактор играет сейчас едва ли не определяющую роль.
Поздравляю вас)
Но хочу сказать, что всё-таки у каждого языка своя ниша и не стоит противопоставлять C++ и Rust.
Меня поразило, как часто код на Rust работает правильно с первого раза, и как просто на нем писать асихронные и многопоточные приложения.

С++ и не задумывался как язык, который позволяет писать элементарный многопоточный код. Напоминаю, что девиз языка «Не плати за то, что не используется», поэтому С++ напоминает конструктор из бесконечного числа мельчайших деталей. Тем-более, в языке изначально не было заложено таких концепций, как «асинхронность» и «потоки», и были притянуты за уши в std лишь в C++11.
Со своими задачами C++ прекрасно справляется. Его стоит использовать там, где требования согласуются с его философией. С Rust точно так же…
И ничего удивительного нет в том, что в Rust X-фича сделана грамотнее, чем в плюсах.
При этом, я уверен, что в обоих языках есть свои плюсы и минусы. Но сравнивать их не нужно. У них абсолютно разная философия и область применения.
С++ и не задумывался как язык, который позволяет писать элементарный многопоточный код. Напоминаю, что девиз языка «Не плати за то, что не используется», поэтому С++ напоминает конструктор из бесконечного числа мельчайших деталей. Тем-более, в языке изначально не было заложено таких концепций, как «асинхронность» и «потоки», и были притянуты за уши в std лишь в C++11.

Rust тут ничем не отличается от плюсов. То же самое "не плати за то, что не используешь", тот же самый конструктор. Только вот основой этого конструктора являются алгебраические типы данных, нормальный параметрический полиморфизм, классы типов, линейные типы для контроля за сохранностью памяти и мощная система макросов для того, что сложно реализовать используя лишь то, о чём сказано выше. Работа с потоками не является частью компилятора, она построена поверх системы типов.


Со своими задачами C++ прекрасно справляется. Его стоит использовать там, где требования согласуются с его философией.

А можно поконкретнее про задачи и философию C++?


И ничего удивительного нет в том, что в Rust X-фича сделана грамотнее, чем в плюсах.

И правда ничего удивительного, ведь Rust появился значительно позже. У его разработчиков была возможность посмотреть на ошибки прошлого и, используя те знания и опыт которые человечество накопило в области дизайна ЯП и компиляторостроения, как-то исправить их. У комитета такой возможности нет, ибо на C++ лежит огромный груз обратной совместимости.


При этом, я уверен, что в обоих языках есть свои плюсы и минусы. Но сравнивать их не нужно. У них абсолютно разная философия и область применения.

Как раз таки область применения у Rust (в перспективе) и C/C++ примерно одна и та же, разве что, ИМХО, у Rust она значительно шире.

И за счет чего у раста область применения станет шире, чем «любая платформа»?
А можно поконкретнее про задачи и философию C++?

например, любые приложения с не глобальными кастомными аллокаторами уже не перепишешь на раст. В т.ч. огромный пласт игровых движков. Всё, что связано с constexpr. А если вам интересно подробнее — в конце статьи есть даже ссылка на видео
И за счет чего у раста область применения станет шире, чем «любая платформа»?

Ну, веб-сайты на плюсах сейчас вроде не верстают. А вот шаблонизаторы на C#/Rust есть.

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

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

редко, но верстают. QtWebGL, например, делают специально для этого.
В rust аллокаторы точно так же отключаются, например вот аллокатор который используется для компиляции в wasm.

не глобальными кастомными аллокаторами

Имелось в виду переопределение аллокатора только в рамках одного класса
Имелось в виду переопределение аллокатора только в рамках одного класса

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

Ну, веб-сайты на плюсах сейчас вроде не верстают.

Не то, чтобы они были распространены, но все же есть.
Как пример — https://cutelyst.org/

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

Вот прям в промышленных масштабах, а не в качестве эксперимента/нишевого проекта? Потому что я вижу много компаний, где даже новый проект спокойно могут на ASP.Net Core начать делать, но с трудом представляю себе реальный кейз "а давайте на плюсах запилим!". Да, сейчас больше есть тенденций на разделение фронт — реакт/ангуляр/вуй и сервер — что угодно, но и первый сценарий использования всё-в-одном достаточно популярен.

не в качестве эксперимента/нишевого проекта?
Так я же сказал: в специфических нишах. О том, что Web-морды для умных устройств делают на C++ рассказывали разработчики Wt и, если правильно помню, разработчики POCO. Так что это не единичные случаи. Но, по сравнению с массовым формошлепством для Web-а, конечно, это капля.
веб-игры часто пишут на плюсах.
не глобальные кастомные аллокаторы, constexpr

По больному бьёте. Тут да, подвижки пока очень медленные. Из первого пока только арены, а со вторым пока приходится полагаться на оптимизации LLVM.

На работе пришлось последний месяц-два писать на Rust, и после этого C++ кажется жутко неудобным и неотёсанным поделием

При дальнейшем использовании впечатления могут меняться. (:


Если что, пишу на расте (фултайм, за деньги) чуть больше года.

Охотно верю. Самому интересно, как изменится мое восприятие через год.
Все тут про Rust указатели и другие вещи, хотя в статье про сигма типы, а в Delphi Variant то был очень давно и я как раз из-за «развитости» языка на С++ и перешел, что средствами языка можно почти все сделать, а тут на тебе под дых, но думаю со временем все решится, все-равно у нас только переходят с 03 на 11, а до 17-20 уууу еще не скоро.

Ссылки на лекции Мэйерса, упомянутые в статье, побились =( Может быть у кого-то остались?

Sign up to leave a comment.

Articles