LINUX.ORG.RU

C++ общая переменная у двух замыканий

 , ,


0

1

Некоторое время назад я читал Javascript: The Good Parts и узнал, что в джаваскрипте нет уровней приватности членов, поэтому там делают энкапсуляцию с помощью лямбд. Я задумался, можно ли сделать так же в C++?

Простейший случай с одним замыканием - можно:

#include <iostream>

auto get_counter() {
    int i {0};
    return [=]() mutable {
        return i++;
    };
}

int main() {
    auto counter = get_counter();
    for (auto i = 0; i < 6; ++i) {
        std::cout << counter() << std::endl;
    }
}

// выведет 0, 1, 2, 3, 4, 5,

Но в джаваскрипте я могу сделать что-то еще интереснее:

var create_counter = function() {
    var i = 0;
    var increment = function() {
        i++;
        return i;
    };
    var reset = function() {
        i = 0;
    };
    var counter = {
        increment: increment,
        reset: reset
    };
    return counter;
};

var counter = create_counter();
for (var j = 0; j < 5; j++) {
    console.log(counter.increment());
}
counter.reset();
console.log(counter.increment());

Выведет

1
2
3
4
5
1

Видно, что у функций increment и reset захвачена общая переменная i, и они ее могут обе читать и менять. Как так же сделать в C++?

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

Захватить её?

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

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

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

Да, ты прав. Придется делать шаред поинтер и захватывать его копии. Эх, можно было бы в C++ делать анонимные объекты, не создавая класс - было бы не намного длиннее, чем на джаваскрипте или питоне.

#include <iostream>
#include <functional>

struct Counter {
    std::function<int()> increment;
    std::function<void()> reset;

    Counter(
        std::function<int()> increment,
        std::function<void()> reset
    ): increment(increment), reset(reset) {}
};

auto get_counter() {
    auto i = std::make_shared<int>(0);
    auto increment = [=]() {
        return ++(*i);
    };
    auto reset = [=]() {
        (*i) = 0;
    };

    return Counter(increment, reset);
}

int main() {
    auto counter = get_counter();
    for (auto i = 0; i < 6; ++i) {
        std::cout << counter.increment() << ", ";
    }
    std::cout << std::endl;
    counter.reset();
    std::cout << counter.increment() << std::endl;
} 
// выводится
// 1, 2, 3, 4, 5, 6, 
// 1

Пожалуй, успех. Можно сделать скрытую приватную переменную, не используя слово private.

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

Эх, можно было бы в C++ делать анонимные объекты, не создавая класс - было бы не намного длиннее, чем на джаваскрипте или питоне.

Можно.

#include <iostream>
#include <functional>
#include <memory>

auto get_counter() {
	auto i = std::make_shared<int>(0);
	auto increment = [=]() {
		return ++(*i);
	};
	auto reset = [=]() {
		(*i) = 0;
	};
	struct Counter {
		std::function<int()> increment;
		std::function<void()> reset;

		Counter(
		            std::function<int()> increment,
		            std::function<void()> reset
		            ): increment(increment), reset(reset) {}
	};

	return Counter(increment, reset);
}

int main() {
	auto counter = get_counter();
	for (auto i = 0; i < 6; ++i) {
		std::cout << counter.increment() << ", ";
	}
	std::cout << std::endl;
	counter.reset();
	std::cout << counter.increment() << std::endl;
} 

UPD. Тут конструктор можно совсем выпилить и делать Counter {increment, reset}.

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

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

P.S. интересно, какого черта оно у меня с make_shared компилируется и без инклюдинга memory

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

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

#include <tuple>
#include <functional>
#include <iostream>
#include <memory>

int main()
{
    auto create_counter = []() ->auto {

        auto i = std::make_shared<int>(0);

        auto increment = [i]() {
            return (*i)++;
        };

        auto reset = [i] {
            (*i) = 0;
        };

        return std::make_tuple(increment, reset);
    };

    auto counter = create_counter();
    for (auto i = 0; i < 5; i++) {
        std::cout << std::get<0>(counter)() << std::endl;
    }

    std::get<1>(counter)();
    std::cout << std::get<0>(counter)() << std::endl;

    return 0;
}

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

Именованные таплы? То есть что-то типа yoba_boost::get<"increment">(counter)? Такое разве вообще возможно? Аргументами шаблонов разве могут быть строки?

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

Хз, я очень бегло погуглил - есть реализации в Poco и во фьюжн.

Если интересно - глянь, заодно и нам расскажешь :)

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

Не особо, из-за того, что структуры не умеют переменные неявно захватывать. А decltype и в D можно сделать, но создать объект этого класса не выйдет, потому что он локальные переменные может неявно захватить (а C++ не может).

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

Я не понял. Так я в C++ потом с помощью деклтайпа могу создать еще экземпляр вольдемортного класса или нет? Я про D ничего не знаю.

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

Например, вот наколеночная реализация, похожая на правду.

Глядишь к 17ому что нибудь в таком духе - окажется в стандарте.

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

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

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

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

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

Ну ессно это надо облагородить, сдобрить макросами и оформить в виде либы.

Этож кресты, тут всё в виде либы.

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

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

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

Таплы придуманны не для этого.

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

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

удалять/вставлять элементы

Вот это точно нет. Таплы это как раз таки контейнеры константной длины.

итерироваться

В C++ - нельзя.

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

Класс написать и не насиловать себе и людям мозг

После этого сообщения нужно было и закрыть тред.

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

Пока читал ОП, думал, что анонимус

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

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