Pull to refresh
6
0
Send message

приведите примеры использования двух подходов, если несложно

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

И еще: в С++ есть тонкость:


  • шаблонный конструктор НЕ замещает стандартный копирующий конструктор. При точном соответствии типов будет вызван обычный копирующий конструктор.
  • шаблонный конструктор никогда не используется для генерации обычного конструктора копий, при его отсутствии, он будет сгенерирован неявно (если только явно не запрещен).
    Если память не изменяет, Страуструп, "Язык программирования С++" 4 издание, глава 13, первый пункт из стандарта (тоже на память)

Вот пример:


#include <QDebug>

class Test {
public:
    Test()
    {
        qDebug() << __PRETTY_FUNCTION__;
    }

    Test(const Test &)
    {
        qDebug() << __PRETTY_FUNCTION__;
    }

    Test(Test &&)
    {
        qDebug() << __PRETTY_FUNCTION__;
    }
private:
};

template<typename T> class immutable
{
private:
    T const value;
public:
    immutable(immutable const &) = delete;
    void operator = (immutable const &) = delete;

    template<typename... Args> immutable(Args&&... args)
        : value(std::forward<Args>(args)...)
    {
        qDebug() << __PRETTY_FUNCTION__;
    }

    T const & operator () () const {
         return value;
    }

    T const * operator -> () const {
         return &value;
    }
};

int main(int argc, char *argv[])
{
    Test t1;
    Test t2;

    immutable<Test> a(t1);
    immutable<Test> b(std::move(t2));

    return 0;
}

Результат:
Test::Test()
Test::Test()
Test::Test(const Test&)
immutable::immutable(Args&& ...) [with Args = {Test&}; T = Test]
Test::Test(Test&&)
immutable::immutable(Args&& ...) [with Args = {Test}; T = Test]

std::forward приводит свой аргумент к rvalue только тогда, когда этот аргумент связан с rvalue. В этом случае value буден сконструировано через вызов конструктора перемещения.
В противном случае value будет сконструировано через конструктор копии.


Если Вы мне не верите, то предлагаю убедиться: С. Майерс "Современный и эффективный С++", главы 5 и 8

спасибо за минус
К https://habrahabr.ru/post/322208/#comment_10085776
Копирование появилось в шаблонном конструкторе: инициализирующее значение, копируется или перемещается в value.

Вы забыли про оператор разыменования *

похоже на std::reference_wrapper…
у Вас появилось копирование, против которого уже негативно высказывались

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


N1570 ISO/IEC 9899:201x

Спасибо, пофиксил. Ставлю +

Приведите, пожалуйста пример нормальной реализации шаблона Immutable.
Хотя бы для значений… \
И не обязательно по букве и духу ФП. Хотя желательно, чтобы с "завернутым" объектом можно было работать как с оригинальным.
Заранее благодарен.

Предчувствую очередной минус, но для 0serg за его труд и аргументированность отвечу.


1) Цель статьи рассказать про концепт ФП — иммутабельные данные. Про Map, Reduce, Filter, карринг и т.п. много написано, но про иммутабельные данные, которые в ФП являются одним из краеуголных камней, сложно что-то найти. Про операции над списками много статей, а реализации некоторых дают немалый оверхед.


2) ФП в С++ смотрится странно, а некоторые его концепты вызывают недоумение. Но и они находят свое примение.


3) D не мой основной язык, просто в стиле "Практический подход к решению проблем программирования C++" (Автор: Мэтью Уилсон) предложил как можно реализовать immutable.


4) Почему-то все комментарии прочитать про D заминусованы, хотя из D в С++ уже пришли шаблоны с переменным числом аргументов, static_assert. В С++ 17 придет constexpr if. Да, то из D, в адаптированной для С++ форме.
В книге "Язык программирования D" от Александреску очень хорошо написано про иммутабельные данные (глава 8).


5) Теперь по сути. Все стали критиковать недостатки value. А достоинства шаблона Immutable<> как-то ускользнули. К value я вернусь попозже.
Например, использование к указателям.


void foo(Immutable<int*> &a)
{
    int *c = new int(100500);
    Immutable<int*> b(c);
    *a = 0; // error
    a = b; //error
    //...
}

Оверхед от Immutable<> меньше чем от smart pointers. А для компиляторов, которые устраняют лишнее копирование (стандартное требование в С++17), его либо нет, либо очень мал. Потому что, шаблонная обертка выкидывается Gimplifier (это под капотом gcc) в gcc.


6) Почему оставлены модифицирующие функции для value?


  • Функция может что-то возвращать что-то интересное для нас (заминусовали);
  • Сохранение семантики (заминусовали);
  • Некоторый контекст (какая-нибудь библиотека) может потребовать наличия данной функции;
  • Некоторые функции в какой-то библиотеке (которую нельзя менять) не меняют объект (явно), но не объявлены константными (разгильдяйство, старый код и т.д.)
  • В общем случае эти функции сложно запретить (не будем же нежелательные функции убирать в private и там писать using Class::function). Более подробно расскажу в примере с заместителем:
  • нужно сделать класс (защитный заместитель), или еще лучше что-то вроде шаблона optional (C++ 17, см. предложения Страуструпа), который должен быть похож на замещаемый класс. Вот здесь в дело и идет CRTP плюс перегрузка оператора. (за его отсутствие у меня используется ()).
    Если нужны более объемные примеры, то поищу (у меня сейчас делается еще одна статья, плюс готовится цикл статей по написанию собственного компилятора в доступном изложении).

Мои, самые вдумчивые читатели, верным путем идете, товарищи, вот пища для ума, основанная на Ваших примерах:


void foo(const Immutable<int> &a, Immutable<int> &b)
{
    a = 42;
    b = 42;
    a = b;
    b = a;

    const_cast<Immutable<int>&>(a) = 100;
    a = Immutable<int>(500);
    b = Immutable<int>(500);
    a = std::move(Immutable<int>(500));
    b = std::move(Immutable<int>(500));
}

Каждое присваивание даст ошибку компиляции. Шаблон Immutable<> дает еще один уровень защиты.
Можно один раз объект обернуть в Immutable<>, а дальше использовать по ссылке и никаких лишних копирований.

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

Не понял к чему это: вы меняете неконстантную ссылку

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


QVector<Immutable<int>> imm = {
           Immutable<int>(1),
           Immutable<int>(2),
           Immutable<int>(3),
           Immutable<int>(4),
           Immutable<int>(5),
       };

Stream<decltype(imm)> t(imm);
qDebug() << t.map(static_cast<QString(*)(int, int)>(&QString::number), 2)
            .filter(std::bind(
                        std::logical_not<>(),
                        std::bind(QString::isEmpty, std::placeholders::_1))
                    )
            .map(&QString::length)
            .filter(std::greater<>(), 1)
            .filter(std::bind(
                        std::logical_and<>(),
                        std::bind(std::greater_equal<>(), std::placeholders::_1, 1),
                        std::bind(std::less_equal<>(),    std::placeholders::_1, 100)
                        )
                    )
            .reduce(std::multiplies<>(), 1);

3) в конце статьи упомянуто, что такое не надо делать;
4) манипулируя с адресом можно сделать все что угодно. Даже в Java через механизм рефлексии можно напакостить;
5) меньше побочных эффектов, т.к. работаете с копией.

Смысл в сохранении естественной семантики работы с объектом. setX может что-то вернуть и это что-то может использоваться далее. В С++ пока нету перегрузки оператора. (пока нету, может быть в середине марта будет решение), который позволит реализовывать умные ссылки, поможет делать защитные заместители и декораторы.
Смотрите, вы передаете в другую функцию: внутри можно изменить с помощью const_cast, игры с адресами и т.д. При работе с Immutable вы работаете с копией, а не с оригиналом. Прочитайте главу 8.1 «Язык программирования D», автор А. Александреску.
Иммутабельные данные в параллельном программировании в разы снижают количество ошибок, за счет меньшего числа побочных эффектов.
еще раз: есть такая нехорошая вешь как const_cast. Посмотрите «Язык программирования D» и перечитайте введение
помните про const_cast
1

Information

Rating
Does not participate
Registered
Activity