Russian Qt Forum
Апрель 30, 2024, 23:34 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 [2]   Вниз
  Печать  
Автор Тема: cow_ptr (полиморфная шара)?  (Прочитано 13337 раз)
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #15 : Февраль 07, 2017, 12:01 »

Набросал на скорую руку такое решение.
Такое поведение имелось в виду?

Код
C++ (Qt)
#include <memory>
 
class AbstractHolder
{
public:
   using SharedAbstractHolder = std::shared_ptr< AbstractHolder >;
 
public:
   virtual ~AbstractHolder () {}
   virtual SharedAbstractHolder clone () const = 0;
};
 
template < typename _Type >
class Holder
   : public AbstractHolder
{
   using ThisType = Holder< _Type >;
   using ParentType = AbstractHolder;
 
public:
   using ValueType = _Type;
 
private:
   ValueType m_value;
 
public:
   template < typename ... _Arguments >
   Holder ( _Arguments && ... args )
   : m_value( std::forward< _Arguments >( args ) ... )
   {
   }
 
   const _Type & readable () const
   {
       return m_value;
   }
 
   _Type & writable ()
   {
       return m_value;
   }
 
   virtual SharedAbstractHolder clone () const
   {
       return std::make_shared< ThisType >( m_value );
   }
};
 
 
template < typename _Type >
class ImplicitWrapper
{
private:
   using ThisType = ImplicitWrapper< _Type >;
   using HolderType = Holder< _Type >;
   using SharedHolder = std::shared_ptr< AbstractHolder >;
   using SharedAbstractHolder = std::shared_ptr< AbstractHolder >;
 
   template < typename _OtherType >
   friend class ImplicitWrapper;
 
private:
   SharedHolder m_shared_holder;
 
private:
   ImplicitWrapper ( const SharedHolder & holder )
   : m_shared_holder( holder )
   {
   }
 
   void detach ()
   {
       if ( m_shared_holder.use_count() > 1 )
           m_shared_holder = m_shared_holder->clone();
   }
 
public:
   ImplicitWrapper ()
   : m_shared_holder()
   {
   }
 
   template < typename _OtherType >
   ImplicitWrapper ( const ImplicitWrapper< _OtherType > & other )
   : m_shared_holder( other.m_shared_holder )
   {
       static_assert( std::is_base_of< _Type, _OtherType >::value, "The types are not compatibile." );
   }
 
   bool isNull () const
   {
       return !m_shared_holder;
   }
 
   const _Type & readable () const
   {
       return std::static_pointer_cast< HolderType >( m_shared_holder )->readable();
   }
 
   _Type & writable ()
   {
       detach();
       return std::static_pointer_cast< HolderType >( m_shared_holder )->writable();
   }
 
public:
   template < typename ... _Arguments >
   static ThisType make ( _Arguments && ... args )
   {
       return ThisType( std::make_shared< Holder< _Type > >( std::forward< _Arguments >( args ) ... ) );
   }
};
 
#include <iostream>
 
struct Test
{
   Test ()
   {
       std::cout << "Constructor of Test" << std::endl;
   }
 
   Test ( const Test & )
   {
       std::cout << "Copy of Test" << std::endl;
   }
 
   Test ( Test && )
   {
       std::cout << "Move of Test" << std::endl;
   }
 
   virtual ~Test ()
   {
       std::cout << "Destructor of Test" << std::endl;
   }
 
   virtual void method ()
   {
       std::cout << "Mutable method of Test" << std::endl;
   }
 
   virtual void method () const
   {
       std::cout << "Constant method of Test" << std::endl;
   }
};
 
struct DerivedTest
   : public Test
{
   DerivedTest ()
   {
       std::cout << "Constructor of DerivedTest" << std::endl;
   }
 
   DerivedTest ( const DerivedTest & other )
   : Test( other )
   {
       std::cout << "Copy of DerivedTest" << std::endl;
   }
 
   DerivedTest ( DerivedTest && other )
   : Test( std::forward< Test >( other ) )
   {
       std::cout << "Move of DerivedTest" << std::endl;
   }
 
   virtual ~DerivedTest ()
   {
       std::cout << "Destructor of DerivedTest" << std::endl;
   }
 
   virtual void method ()
   {
       std::cout << "Mutable method of DerivedTest" << std::endl;
   }
 
   virtual void method () const
   {
       std::cout << "Constant method of DerivedTest" << std::endl;
   }
};
 
struct OtherTest
{
   OtherTest ()
   {
       std::cout << "Constructor of OtherTest" << std::endl;
   }
 
   ~OtherTest ()
   {
       std::cout << "Destructor of OtherTest" << std::endl;
   }
 
   void method ()
   {
       std::cout << "Mutable method of OtherTest" << std::endl;
   }
 
   void method () const
   {
       std::cout << "Constant method of OtherTest" << std::endl;
   }
};
 
int main ( int, char ** )
{
   // OK
   {
       ImplicitWrapper< Test > test_value = ImplicitWrapper< Test >::make();
       test_value.readable().method();
       test_value.writable().method();
   }
 
   // OK
   {
       ImplicitWrapper< Test > test_value = ImplicitWrapper< DerivedTest >::make();
       test_value.readable().method();
       test_value.writable().method();
   }
 
   // ERROR
//    {
//        ImplicitWrapper< Test > test_value = ImplicitWrapper< OtherTest >::make();
//        test_value.readable().method();
//        test_value.writable().method();
//    }
 
   // OK
   {
       ImplicitWrapper< DerivedTest > first_value = ImplicitWrapper< DerivedTest >::make();
       ImplicitWrapper< Test > second_value = first_value;
       second_value.readable().method();   // not detached
       second_value.writable().method();     // detached
   }
 
   // OK
   {
       ImplicitWrapper< Test > second_value = ImplicitWrapper< DerivedTest >::make( DerivedTest() );
       second_value.readable().method();
       second_value.writable().method();
   }
 
   return 0;
}
 
« Последнее редактирование: Февраль 07, 2017, 12:27 от ssoft » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #16 : Февраль 07, 2017, 13:35 »

Первое - нужен "вумный" указатель, который полиморфно клонирует объект
Код
C++ (Qt)
template<class T>
class MyPtr
{
   T *d;
   MyPtr(const MyPtr &other) : d(other.d->clone()) {}
};
 
А разве такое клонирование полиморфно? По-моему нет, ведь other может быть лишь того же типа T. Поправьте если не так

Второе - собсно обычная корова (QSharedData+QSharedDataPointer), которая управляет МуПтром.
Да, но делегировать на "d" хлопотно. Сравним с "нулевым вариантом" (голый указатель). В принципе и там все решаемо за 5-10 минут. Ну добавил по строчке в конструктор/деструктор. С копированием правда хужее - придется считать что BaseData имеет виртуальный метод clone(). Вот когда все эти сопельки убираются "легким движением руки" - гуд. А начинать долгую писанину из-за этого непривлекательно.

Набросал на скорую руку такое решение.
Такое поведение имелось в виду?
К сожалению, все никак не переползу на С++ 11, но по тексту вижу - да, такое. Никак нельзя убрать (или иначе оформить) эти readable/writeable? Спасибо

 

Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #17 : Февраль 07, 2017, 14:17 »

Набросал на скорую руку такое решение.
Такое поведение имелось в виду?
К сожалению, все никак не переползу на С++ 11, но по тексту вижу - да, такое. Никак нельзя убрать (или иначе оформить) эти readable/writeable? Спасибо

Для ранних С++ тоже можно написать решение, только кода будет значительно больше, в основном из-за "_Arguments ... args". Сделать основу?

Совсем от readable/writable избавиться не получится.

Либо придется следить за const так

Код
C++ (Qt)
ImplicitWrapper< Test > mutable_value = ImplicitWrapper< Test >::make();
mutable_value->method(); // всегда mutable метод
 
ImplicitWrapper< const Test > const_value = mutable_value;
const_value->method(); // всегда cosnt
 

Либо так

Код
C++ (Qt)
// value не предоставляет доступ к Test
ImplicitWrapper< Test > value = ImplicitWrapper< Test >::make();
 
WriteAccessor< Test > writer( value );
writer->method(); // всегда mutable метод
writer.value().method(); // всегда mutable метод
 
ReadAccessor< Test > reader( value );
reader->method(); // всегда const метод
reader.value().method(); // всегда const метод
 

Либо так

Код
C++ (Qt)
// value не предоставляет доступ к Test
ImplicitWrapper< Test > value = ImplicitWrapper< Test >::make();
 
writable( value ).method(); // всегда mutable метод
readable( value ).method(); // всегда const метод
 

Либо как изначально

Код
C++ (Qt)
ImplicitWrapper< Test > value = ImplicitWrapper< Test >::make();
 
value.writable().method(); // всегда mutable метод
value.readable().method(); // всегда const метод
 

Выберете один из. Мне больше нравится третий, так как этот вариант легче всего переопределить под произвольный тип (как std::begin/std::end). Но это мое субъективное мнение.
Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #18 : Февраль 07, 2017, 14:58 »

Для QSharedPointer и более ранних С++

Код
C++ (Qt)
#include <memory>
#include <QSharedPointer>
 
class AbstractHolder
{
public:
   typedef QSharedPointer< AbstractHolder > SharedAbstractHolder;
 
public:
   virtual ~AbstractHolder () {}
   virtual SharedAbstractHolder clone () const = 0;
};
 
template < typename _Type >
QSharedPointer< _Type > makeShared()
{
   return QSharedPointer< _Type >( new _Type );
}
 
template < typename _Type, typename _Arg0 >
QSharedPointer< _Type > makeShared( const _Arg0 & arg0 )
{
   return QSharedPointer< _Type >( new _Type( arg0 ) );
}
 
// etc ... _Arg1, _Arg2 ...
 
template < typename _Type >
class Holder
   : public AbstractHolder
{
   typedef Holder< _Type > ThisType;
   typedef AbstractHolder ParentType;
 
public:
   typedef _Type ValueType;
 
private:
   ValueType m_value;
 
public:
   Holder ()
   : m_value()
   {
   }
 
   template < typename _Arg0 >
   Holder ( const _Arg0 & arg0 )
   : m_value( arg0 )
   {
   }
 
   // etc ... _Arg1, _Arg2 ...
 
   const _Type & readable () const
   {
       return m_value;
   }
 
   _Type & writable ()
   {
       return m_value;
   }
 
   virtual SharedAbstractHolder clone () const
   {
       return makeShared< ThisType >( m_value );
   }
};
 
 
template < typename _Type >
class ImplicitWrapper
{
private:
   typedef ImplicitWrapper< _Type > ThisType;
   typedef Holder< _Type > HolderType;
   typedef QSharedPointer< AbstractHolder > SharedAbstractHolder;
 
   template < typename _OtherType >
   friend class ImplicitWrapper;
 
private:
   SharedAbstractHolder m_shared_holder;
 
private:
   ImplicitWrapper ( const SharedAbstractHolder & holder )
   : m_shared_holder( holder )
   {
   }
 
   void detach ()
   {
       // Hack!!! Qt4 protected, Qt5 private
       struct HackValue
       {
           struct Data
           {
               QBasicAtomicInt weakref;
               QBasicAtomicInt strongref;
           };
 
           AbstractHolder * m_type;
           Data * m_data;
       };
       HackValue * hack = reinterpret_cast< HackValue * >( &m_shared_holder );
 
       if ( hack->m_data->strongref > 1 )
           m_shared_holder = m_shared_holder->clone();
   }
 
public:
   ImplicitWrapper ()
   : m_shared_holder()
   {
   }
 
   template < typename _OtherType >
   ImplicitWrapper ( const ImplicitWrapper< _OtherType > & other )
   : m_shared_holder( other.m_shared_holder )
   {
       Q_ASSERT( !other.m_shared_holder
           || static_cast< _Type * >( const_cast< _OtherType * >( &other.readable() ) ) );
   }
 
   bool isNull () const
   {
       return !m_shared_holder;
   }
 
   const _Type & readable () const
   {
       return m_shared_holder.staticCast< HolderType >()->readable();
   }
 
   _Type & writable ()
   {
       detach();
       return m_shared_holder.staticCast< HolderType >()->writable();
   }
 
public:
   static ThisType make ()
   {
       return ThisType( makeShared< Holder< _Type > >() );
   }
 
   template < typename _Arg0 >
   static ThisType make ( const _Arg0 & arg0 )
   {
       return ThisType( makeShared< Holder< _Type > >( arg0 ) );
   }
 
   // etc ... _Arg1, _Arg2 ...
};
 
#include <iostream>
 
struct Test
{
   Test ()
   {
       std::cout << "Constructor of Test" << std::endl;
   }
 
   Test ( const Test & )
   {
       std::cout << "Copy of Test" << std::endl;
   }
 
   virtual ~Test ()
   {
       std::cout << "Destructor of Test" << std::endl;
   }
 
   virtual void method ()
   {
       std::cout << "Mutable method of Test" << std::endl;
   }
 
   virtual void method () const
   {
       std::cout << "Constant method of Test" << std::endl;
   }
};
 
struct DerivedTest
   : public Test
{
   DerivedTest ()
   {
       std::cout << "Constructor of DerivedTest" << std::endl;
   }
 
   DerivedTest ( const DerivedTest & other )
   : Test( other )
   {
       std::cout << "Copy of DerivedTest" << std::endl;
   }
 
   virtual ~DerivedTest ()
   {
       std::cout << "Destructor of DerivedTest" << std::endl;
   }
 
   virtual void method ()
   {
       std::cout << "Mutable method of DerivedTest" << std::endl;
   }
 
   virtual void method () const
   {
       std::cout << "Constant method of DerivedTest" << std::endl;
   }
};
 
struct OtherTest
{
   OtherTest ()
   {
       std::cout << "Constructor of OtherTest" << std::endl;
   }
 
   ~OtherTest ()
   {
       std::cout << "Destructor of OtherTest" << std::endl;
   }
 
   void method ()
   {
       std::cout << "Mutable method of OtherTest" << std::endl;
   }
 
   void method () const
   {
       std::cout << "Constant method of OtherTest" << std::endl;
   }
};
 
int main ( int, char ** )
{
   int test_number = 0;
   // OK
   {
       std::cout << "Test - " << (test_number++) << std::endl;
       ImplicitWrapper< Test > test_value = ImplicitWrapper< Test >::make();
       test_value.readable().method();
       test_value.writable().method();
   }
 
   // OK
   {
       std::cout << "Test - " << (test_number++) << std::endl;
       ImplicitWrapper< Test > test_value = ImplicitWrapper< DerivedTest >::make();
       test_value.readable().method();
       test_value.writable().method();
   }
 
   // OK
   {
       std::cout << "Test - " << (test_number++) << std::endl;
       ImplicitWrapper< const Test > test_value = ImplicitWrapper< DerivedTest >::make();
       test_value.readable().method();
       test_value.writable().method();
   }
 
   // ERROR
//    {
//    std::cout << "Test - " << (test_number++) << std::endl;
//        ImplicitWrapper< Test > test_value = ImplicitWrapper< OtherTest >::make();
//        test_value.readable().method();
//        test_value.writable().method();
//    }
 
   // OK
   {
       std::cout << "Test - " << (test_number++) << std::endl;
       ImplicitWrapper< DerivedTest > first_value = ImplicitWrapper< DerivedTest >::make();
       ImplicitWrapper< Test > second_value = first_value;
       second_value.readable().method();   // not detached
       second_value.writable().method();     // detached
   }
 
   // OK
   {
       std::cout << "Test - " << (test_number++) << std::endl;
       ImplicitWrapper< Test > second_value = ImplicitWrapper< DerivedTest >::make( DerivedTest() );
       second_value.readable().method();
       second_value.writable().method();
   }
 
   return 0;
}
 
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #19 : Февраль 08, 2017, 08:48 »

...в основном из-за "_Arguments ... args". Сделать основу?
Спасибо, не нужно, эти "вариадики" меня так пугают...

А вот такая (экспериментальная) мысль - а не задействовать ли и здесь weak_ptr (вероятно его аналог). Guard - ценная возможность, ну может detach при его создании...
Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #20 : Февраль 08, 2017, 15:54 »

А вот такая (экспериментальная) мысль - а не задействовать ли и здесь weak_ptr (вероятно его аналог). Guard - ценная возможность, ну может detach при его создании...

Implicit shared обертка реализует композитную уникальную ассоциацию (composite) уникальное представление экземпляра объекта с помощью механизма "ленивых вычислений", когда вычисления производятся только при необходимости. В данном контексте к вычислениям относится необходимость копирования данных объекта при их модификации. Механизм "ленивых вычислений" в данном случае реализуют с помощью ассоциации обобщенной агрегации (shared), однако сам объект, как мы раннее выяснили, реализует композитную уникальную ассоциацию (composite) сама Implicit shared обертка представляет уникальный экземпляр объекта, как если бы была реализована ассоциация уникальной композиции (composite) .

Так как композитная ассоциация уникальное представление экземпляра объекта оберткой реализуется посредством обобщенной агрегации, то такой случай и называется неявное обобщение (Implicit shared).

Касаясь вопроса применения weak_ptr во внутренней реализации обертки, то weak_ptr не реализует агрегацию в принципе - ни композитную, ни обобщенную. weak_ptr реализует ассоциативную связь с возможностью продления времени жизни экземпляра объекта, поэтому в данном месте неприменим.

Необходимо отметить, что для экземпляров объектов под управлением Implicit shared не существует возможности сослаться любым другим способом, так как они могут быть клонированы в любой момент. Однако имеется возможность ссылки на саму Implicit shared обертку.
« Последнее редактирование: Февраль 08, 2017, 18:00 от ssoft » Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #21 : Февраль 08, 2017, 17:27 »

Я бы не стал смешивать ассоциации и Implicit shared. По мне, copy-on-write - это просто техника оптимизации хранения и копирования данных объекта, его внутренние проблемы, и не более. Есть ли в объекте cow, нет ли, на логику создания/удаления, владения, связей между объектами, логику модели это никакого влияния оказывать не должно.
Записан

Пока сам не сделаешь...
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #22 : Февраль 08, 2017, 17:42 »

Я бы не стал смешивать ассоциации и Implicit shared. По мне, copy-on-write - это просто техника оптимизации хранения и копирования данных объекта, его внутренние проблемы, и не более. Есть ли в объекте cow, нет ли, на логику создания/удаления, владения, связей между объектами, логику модели это никакого влияния оказывать не должно.

Да, напрямую не стоит смешивать.
Я имел всего лишь в виду, что Implicit shared обертка представляет сам объект, как уникальный экземпляр, в то время как он на самом деле обобщенный. А ассоциации здесь относятся к внутренней архитектуре самой обертки, как и весь предыдущий мой пост о возможности применения внутри weak_ptr. Поправлю помарки).


Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #23 : Февраль 08, 2017, 18:58 »

По мне, copy-on-write - это просто техника оптимизации хранения и копирования данных объекта, его внутренние проблемы, и не более. Есть ли в объекте cow, нет ли, на логику создания/удаления, владения, связей между объектами, логику модели это никакого влияния оказывать не должно.
Категорически согласен! Корова может быть пожертвована в любой момент, пример
Код:
QVector<int> vec;
...
int test = vec[0];   // detach (хотя никакой необходимости не было)
Я бы не стал смешивать ассоциации и Implicit shared.
Но почему совершенно противоположный вывод Непонимающий  Улыбающийся

Касаясь вопроса применения weak_ptr во внутренней реализации обертки, то weak_ptr не реализует агрегацию в принципе - ни композитную, ни обобщенную. weak_ptr реализует ассоциативную связь с возможностью продления времени жизни экземпляра объекта, поэтому в данном месте неприменим.
Мне кажется Вы слишком увлеклись абстракцией Улыбающийся Есть блочок памяти в котором сидят счетчики и (возможно) хранимый объект. Этот блочок живет пока хотя бы 1 из счетчиков ненулевой. Weak имеет к нему доступ так же как и шаред, просто weak не препятствует удалению объекта. Почему Вы считаете его чем-то принципиально иным?

Не очень понял про "агрегацию" и "ассоциацию", если нетрудно поясните что значат эти термины в данном контексте. Спасибо
Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #24 : Февраль 08, 2017, 20:47 »

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

Это понятия модели ООП.
Ассоциация - это понятие означающее, что экземпляры одного класса связаны с экземплярами другого класса. Также вводится понятие связь (link), как экземпляра ассоциации, которая реализуется в виде, включающем понятие полюсов. Полюсы могут являться частью сущностей, участвующих в ассоциации. Если не вдаваться в подробности, то связи (их полюсы) реализуются всяческими указателями, ссылками или вычисляются runtime.

Ассоциации бывают бинарными, и т.д. N-нарными, в зависимости от количества участвующих сущностей. Чаще всего мы имеем дело с бинарными ассоциациями. Одним из частных случаев бинарной ассоциации является отношение типа "часть ‒ целое", такие ассоциации имеют название агрегации. В такой ассоциации целым (агрегатором) по отношению к части может быть только одна сущность (правила подобны отношению наследования). Целое может агрегировать часть индивидуально (composite) или совместно (shared). Первый случай часто называют композицией. Отношение "часть - целое" регламентирует удаление части вместе с целым (по крайне мере в композиции).

Удобно совместить контроль времени жизни экземпляра объекта с экземпляром полюса, реализующим ассоциативную связь агрегации. Другого вида ассоциативные связи, по моему, логически несовместимы с контролем времени жизни. Хорошо иллюстрирует ассоциативную связь агрегации отношение parent - child, когда child "знает", кто его parent. parent должен обеспечить уникальное владение своими child, а child ссылаться на своего parent.

weak_ptr не реализует полюс ассоциативной связи агрегации со стороны целого (так как не управляет временем жизни экземпляра) , хотя может реализовывать полюс ассоциативной связи агрегации со стороны части.
Wrapper является целым по отношению к хранимому объекту, таким образом внутреннее представление должно реализовывать полюс ассоциации со стороны целого в любом доступном виде.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #25 : Февраль 09, 2017, 12:23 »

Ассоциация - это понятие означающее..
Не все, но кое-что понял. Спасибо за разъяснения.

Насчет аналога weak_ptr.

Посмотрим что с "продлением жизни". Допустим если объект "жив" - увеличиваем число ссылок на него создавая еще один wrapper (cow_ptr). Однако, в отличие от шаред, мы согласились с созданием копии (пусть копирование состоится и не сразу). И можем с копией и остаться, т.е. по сути ничего не продлеваем.

Можно конечно действовать по образцу QPointer, так мы получим raw указатель если живой. Что легко может стать источником ошибок. Напр теперь уже нельзя из него делать wrapper. Ну может ограничить аналог weak методами isNull и константным ->

Мда, как-то становится неуютно когда нет знакомых вещей  Улыбающийся
Записан
Страниц: 1 [2]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.223 секунд. Запросов: 22.