Очень умная ссылка в C++

Всё что здесь описано, попросту сишный трюк, который мало достоин внимания. Его весьма полезно применять для работы со строками, что бы не плодить лишние копии текста. Я сам планировал вставить его в полигональный процессор BSP дерева, когда дойдут руки. Сама идея написать про это, родилась после собеседования с очередным самонадеянным гоблином, во время приёма на работу (устраивался я).

Незамысловатый шаблон, который по функциям похож на умную ссылку из STL, только ведёт он себя немного умнее.
template<class T>
class auto_ref{
	struct item_st{	
		int count;
		T obj;
	}*data_ref;
	void Free(item_st *ref) {
		if(!ref) return;
		if(!--ref->count) delete ref;
	}
	void New() {		
		if(data_ref) return;
		data_ref=new item_st; 
		data_ref->count=1;
	}
public:
	operator =(auto_ref &obj) {
		item_st *temp=data_ref;
		data_ref=obj.data_ref;
		if(data_ref) data_ref->count++;
		Free(temp);
	}
	operator =(const T &value) {
		New();
		data_ref->obj=value;
	}
	T *operator ->() { 
		New(); 
		return &data_ref->obj;
	}
	operator T() {
		New();
		return data_ref->obj;
	}
	auto_ref() { 
		data_ref=0;
	}
	auto_ref(auto_ref &obj) {
		operator=(obj);
	}
	~auto_ref() {
		Free(data_ref);
	}
};

Сама идея данной конструкции давно витает внутри библиотек Qt. В основном применяется для строк. Мой вариант возможно слишком перегружен проверками. Не хочется подробно расписывать его работу, надеюсь всё понятно из исходника. Применять его очень просто:
void Hello()
{
	auto_ptr<double> obj1,obj2;
	obj1=1.7;
	obj2=obj1;
	double f=obj2;

}

В данном случае объекты obj1,obj2 по смыслу и внешним свойствам ничем не будут отличаться от обычной double переменной. Разница в том что на самом деле они содержат в себе ссылку на одно и тоже double число. И само это число будет благополучно удалено во время выхода из функции. Вместо double вы можете подсунуть любой тип, структуру или класс. Так же вы можете приравнивать ссылки друг другу и делать с ними всё что захотите. Ссылка на ваш объект будет попросту дублироваться. Присваивание ссылки самой себе, перекрёстные ссылки, и любые другие фокусы, не смогут нарушить работу по подсчёту и удалению. Корректная работа и удаление уже ненужных объектов, гарантируется абсолютно в любом случае!

Продвинутые примеры:

struct data_st{int a;int b;};

void main()
{
	auto_ref<double>ref1,ref2;
	auto_ref<data_st>ref3,ref4;
	ref1=ref2;
	ref1=3.1415926;
	ref3->a=1;
	ref2=ref1;
	ref1=ref2;
	ref1=ref1;
	ref4=ref3;
	ref3=ref4;
	double f=ref2;
	ref4->b=ref4->a;
}

Файл

  • auto_ref.h

    Глава 2

    Более быстрый указатель, из которого выкинуты все проверки. Но в обращении он немного сложнее и уже требует некоторой осторожности. Теперь есть два вида указателей auto_ref и _auto_ref. По функциям и свойствам между ними нет никакой разницы. Но _auto_ref в момент создания не инициализирует свой экземпляр данных и не может служить источником ссылки.

    struct data_st{int a;int b;};
    
    void main()
    {
    	auto_ref<item_st>ref1;
    	_auto_ref<item_st>ref2;
    	// ref1=ref2; - недопустимо (ref2 не может быть источником)
    	ref2=ref1;
    	ref1=ref2;
    	ref1=ref1;
    	ref2=ref2;
    

    Само присваивание данных от ссылки которая не является источником, не вызовет проблем. Но вот обращение к данным, которые должна представлять ссылка, вызовет падение программы. Добавлены две небольшие функции:

    Файл

  • auto_ref2.h

    Глава 3

    Очень интересный момент. Заставить указатель, отвечать за безопастность внутри разных потоков. Один не очень хитрый трюк, позволяет сделать так, чтобы две функции одной копии, которые вызываются через -> не могли выполняться одновременно в разных потоках. Т.е. внутри данных заложен сихронизирующий объект, который блокирует вызов оператора в момент его вызова и снимает блокировку после, того как функция возвращает управление.

    	class auto_ref {
            	....
    		template<T> class Proxy {
    			T *ref;
    		public:
    			Proxy(T* obj):ref(obj) {
    				\\ lock
    			}
    			~Proxy() {
    				\\ unlock
    			}
    			T* operator->() 
    			{
    				return ref;
    			}
    		};
    		Proxy<item_st*>operator ->() { 
    			return Proxy<item_st*>(data_ref);
    		}
    	...
    	};
    

    Во время вызова функции через оператор -> в стеке создаётся временный объект, через который вызывается наш класс. Соотвественно конструктор и деструктор временного объекта, отвечают на блокировку совместного доступа. На примере это работает так:

    	class Test{
    	public:
    		void test() {
    			\\ test
    		}
    	};
    
    	main() {
    		auto_ref<Test>obj;
    		obj->test();
    		...
    	}
    
    Последовательность действий будет такой: lock, test, unlock.

    Разумеется блокировка всего лишь пример создания прокладок для вызова. Для этого фокуса можно придумать и другое полезное применение.

    Файл

    Прилагается тестовый пример, не делающий реальной блокировки. И рабочий windows вариант указателя. Linux варианта, пока что нету (не хочу комп. перезаглужать, и вообще второй день хочу спать). В рабочем варианте, особое внимание следует обратить на то, каким образом увеличивается и уменьшается счётчик использования.
  • auto_ref3.cpp (пример)
  • auto_ref3_win.h (рабочий вариант Windows)
  • auto_ref3_lin.h (рабочий вариант Posix)

    Глава 4

    В Boost и Qt есть подобный шаблон для ссылок с подсчётом. В Qt называется: QSharedDataPointer. Используется только как компонент внутри других классов. Применяется внутри строк, stl-подобных контейнеров и т.д. Т.е. любые строки можно передавать не по ссылке, излишнего копирования данных не происходит. Отдельная копия данных создаётся, только если мы хотил изменить строку. QSharedDataPointer перегружает два варината оператора -> , констатный и обычный вызов:

    T* operator -> ();
    T* operator -> () const;
    
    Константный метод не делает ничего кроме вызова указанной функции (предполагается что вызываемая функция не может изменить данные). Неконстантый автоматически отсоединяет копию данных. Соответственно в чистом виде применять этот шаблон не имеет смысла. Оператор никак не может определить вызывает ли оператор константную функцию или нет. Весь выбор определяется обертками для QSharedDataPointer, внутри константных методов делается обычный вызов объекта по указателю. Внутри не константных методов, перед вызовом автоматически делается копия данных.

    Hosted by uCoz