Skip to content

Latest commit

 

History

History
157 lines (142 loc) · 22.8 KB

Lecture 07.md

File metadata and controls

157 lines (142 loc) · 22.8 KB


Signals

Signals aka listeners aka observers
Не путой с
Unix signals

slot - подписанный на событие

struct signal {
	typesef function <void()> slot;
	void connect(slot s) {
		slots.push_back(move(s));	
	}
<span class="token keyword">void</span> <span class="token keyword">operator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token operator">&amp;&amp;</span><span class="token keyword">auto</span> s<span class="token operator">:</span> slots<span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token function">s</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

private: vector<slot> slots; }

Проблемы:
1. Если s бросает исключение. Пробрасывать его выше не стоит, так как кто вызывает сигнал, обычно не представляет что за ошибка вообще.
2. Как отписываться? function не умеет сравниваться. Можно возвращать id'шник из connect и удалять по нему. Логично использовать двусвязный список.

struct signal {
	typesef function <void()> slot;
	connection connect(slot s) {
		slots.push_back(move(s));	
		return connection(this, --slots.end());
	}
<span class="token keyword">void</span> <span class="token keyword">operator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token operator">&amp;&amp;</span><span class="token keyword">auto</span> s<span class="token operator">:</span> slots<span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token function">s</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
		<span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'...'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

private: list<slot> slots; }

struct signal::connection { connection(signal* sig, list<slot>::iterator it) : sig(sig), it(it) {} disconnect() { sig -> slots.erase(it); }

private: signal* sig; list<slot>::iterator it; }

Но вот большая беда:

void on_timeout() {
	....
	conn.disconnect();
}

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

struct signal {
	/.../
	void operator()() const {
		vector<slot> copy(slots.begin(), slots.end());
		for(&&auto s: copy) { /.../ }
	}
	/.../
}

Новая проблема: если A удалил B, то B все равно вызовется. В такой реализации нет команды отписаться, есть команда отпишись через какое-то время

Попробуем пофиксить:

struct signal {
	/.../
	void operator()() const {
		for(&&auto s: slots) { 
			if(s.first) {
				s.second();
			}
		}
		slots.erase(remove_if(slots.begin(), 
							   slots.end(), 
							   [](pair<bool, slot> const &p) {
							   	  return !p.first;
							   }), 
					 slots.end());
	}

private: list<pair<bool, slot>> slots; }

Опа, проблема (callstack):

signal::op()
|_ A
|  |_ signal::op()
|	 |_ B
|	 |	|_ disconnect()
|	 |_ cleanup
|_ ++i;  - разъименование удаленного итератора

Фиксим еще раз, учитывая рекурсивные вызовы

struct signal {
	/.../
	void operator()() const {
		++rec_counter;
		for(&&auto s: slots) { 
			if(s.first) {
				s.second();
			}
		}
		--rec_counter;
		if(rec_counter == 0) {
			for(auto i = slots.begin(); i != slots.end();) {
				if(i->first) 
					++i;
				else 
					i = slots.erase(i);
			}
		}
	}

private: size_t rec_counter; list<pair<bool, slot>> slots; }

struct signal::connection { /.../ disconnect() { if (sig->rec_counter == 0) sig -> slots.erase(it); else it->first = false; }

private: signal* sig; list<slot>::iterator it; }

Все выше - проблема reentrancy. Если мы рекурсивно (прямо или косвенно) вызываем функцию работы с одними и теми же данными, сложно предсказать работу программы.

f();
log('...')
assert(flag); // Даже тут assert может упасть при удаленных вызовах в log

void f() { flag = true; }