Russian Qt Forum

Программирование => С/C++ => Тема начата: ecspertiza от Ноябрь 28, 2017, 19:56



Название: C++ lazy initialization
Отправлено: ecspertiza от Ноябрь 28, 2017, 19:56
Собственно есть несколько вариантов решения, хочется сделать красиво :-)

Вариант 1, первое что нашел на эту тему, не радует то, что по сути это синглтон. Удалить никак
Код:
class TestClass
{
public:
    static TestClass &get() {
        static TestClass instance;
        return instance;
    }
};

Вариант 2, изобретал на коленке
Код:
template <class T>
class Lazy
{
    public:
        T* operator ->() {
            return GetObject();
        }

    private:
        T *GetObject() {
            if (_instance.isNull()) {
                _instance = QSharedPointer<T>::create();
            }

            return _instance.data();
        }

        QSharedPointer<T> _instance;
};

этот вариант устраивает, так как и инициализация отложенная, только при доступе. И при удалении Lazy объект внутри будет удален, уходим от синглтона. Но в этом случае не нравится что не понятно как передать параметры в конструктор объекта. Все усложняется еще сильнее когда мы не знаем количество параметров в конструкторе объекта.

Есть мысли как использовать вариативный шаблон отложено в этом случае? Или может быть можно еще как то решить задачу?


Название: Re: C++ lazy initialization
Отправлено: Swa от Ноябрь 28, 2017, 22:39
Либо создавать объект на куче либо откладывать инициализацию не всего объекта, а только его больших полей.


Название: Re: C++ lazy initialization
Отправлено: ksk- от Ноябрь 29, 2017, 05:28
Как вариант, можно сохранить параметры конструктора объекта в кортеж, а в методе GetObject() создавать объект из этого кортежа. Но и тут есть минусы. Например, если один и тот же класс имеет несколько конструкторов, то и типы Lazy для них будут различны.


Название: Re: C++ lazy initialization
Отправлено: ksk- от Ноябрь 29, 2017, 05:32
Можно также вместо параметров передавать лямбду, которая будет создавать экземпляр объекта.


Название: Re: C++ lazy initialization
Отправлено: ecspertiza от Ноябрь 29, 2017, 10:07
Либо создавать объект на куче либо откладывать инициализацию не всего объекта, а только его больших полей.

Не очень понятно, чем инициализация на куче будет отличаться от шаред поинтера? Кроме того, что мы сами контролируем создание\удаление объекта

Как вариант, можно сохранить параметры конструктора объекта в кортеж, а в методе GetObject() создавать объект из этого кортежа. Но и тут есть минусы. Например, если один и тот же класс имеет несколько конструкторов, то и типы Lazy для них будут различны.

Была такая мысль, но хочется использовать шаблон

Код:
template<typename... Values>

Может быть можно его сохранить в классе ?


Название: Re: C++ lazy initialization
Отправлено: ksk- от Ноябрь 29, 2017, 10:45
Псевдокод.
Код
C++ (Qt)
template<typename T, typename... Ts>
class Lazy
{
public:
   template<typename... Us>
   explicit Lazy(Us &&... args)
       : args_(std::make_tuple(std::forward<Us>(args)...)) {
   }
 
   T *GetObject() {
       if (!ptr_) {
           ptr_.reset(createObject<T>(args_));
       }
 
       return ptr_.get();
   }
 
private:
   std::tuple<Ts...> args_;
   std::unique_ptr<T> ptr_;
};
 


Название: Re: C++ lazy initialization
Отправлено: Old от Ноябрь 29, 2017, 11:08
У меня пока получилось так:
Код
C++ (Qt)
#include <functional>
#include <memory>
 
using std::shared_ptr;
using std::make_shared;
using std::function;
using std::bind;
 
template <typename T, typename ...Param>
class Lazy
{
using pointer = shared_ptr<T>;
 
public:
explicit Lazy( Param... p ) :
m_builder( bind( &Lazy::build, this, p... ) )
{
}
 
pointer operator ->()
{
return ptr();
}
 
private:
pointer ptr()
{
if( !m_obj )
m_obj = m_builder();
 
return m_obj;
}
 
pointer build( Param... p )
{
return make_shared<T>( p... );
}
 
function<pointer ()> m_builder;
shared_ptr<T> m_obj;
};
 

К сожалению, сразу не получилось избавиться от перечисления всех типов параметров при описании указателя. :(
Код
C++ (Qt)
Lazy<Foo,int> p1( 10 );
cout << p1->a() << endl;
 
Lazy<Foo,int,int> p2( 10, 20 );
cout << p2->a() << endl;
 


Название: Re: C++ lazy initialization
Отправлено: __Heaven__ от Ноябрь 29, 2017, 14:21
В продолжение к посту Old
Код
C++ (Qt)
template <typename T, typename ...Param>
Lazy<T, Param...> makeLazy(Param... p) {
   return Lazy<T, Param...>(p...);
}
 
Код
C++ (Qt)
   auto p1 = makeLazy<Foo>( 10 );
   cout << p1->a() << p1->b() << endl;
 
   auto p2 = makeLazy<Foo>( 10, 20 );
   cout << p2->a() << p2->b() << endl;
 


Название: Re: C++ lazy initialization
Отправлено: __Heaven__ от Ноябрь 29, 2017, 14:27
К сожалению, сразу не получилось избавиться от перечисления всех типов параметров при описании указателя. :(
Мне казалось, что c++17 научили распознавать параметры шаблонного класса... Но почему-то не сработало...


Название: Re: C++ lazy initialization
Отправлено: Old от Ноябрь 29, 2017, 17:05
Вариант с лямбдой выглядит аккуратней: :)
Код
C++ (Qt)
#include <functional>
#include <memory>
 
using std::shared_ptr;
using std::make_shared;
using std::function;
using std::bind;
 
template <typename T, typename ...Param>
class Lazy
{
using pointer = shared_ptr<T>;
 
public:
explicit Lazy( Param... p ) :
m_builder( [=](){ return make_shared<T>( p... ); } )
{
}
 
pointer operator ->()
{
return ptr();
}
 
private:
pointer ptr()
{
if( !m_obj )
m_obj = m_builder();
 
return m_obj;
}
 
function<pointer ()> m_builder;
shared_ptr<T> m_obj;
};
 


Название: Re: C++ lazy initialization
Отправлено: ViTech от Ноябрь 29, 2017, 17:31
Это всё хорошо, только где и как предлагается использовать классы/объекты типа Lazy? кроме как
... либо откладывать инициализацию не всего объекта, а только его больших полей.


Название: Re: C++ lazy initialization
Отправлено: ecspertiza от Ноябрь 29, 2017, 18:44
Код:
template<class T>
class Lazy
{
public:
template<typename... Args>
Lazy(Args ... arguments)
: _instance(nullptr)
, _factory([=]()
{
return new T(arguments...);
})
{
}

~Lazy()
{
if (_instance != nullptr)
{
delete _instance;
}
}

T* operator ->()
{
return GetObject();
}

private:
T* GetObject()
{
if (_instance == Q_NULLPTR)
{
_instance = _factory();
}
return _instance;
}

private:
T* _instance;
std::function<T*()> _factory;
};

как то так получилось :-)


Название: Re: C++ lazy initialization
Отправлено: Old от Ноябрь 29, 2017, 18:51
Такая проверка избыточна:
Код
C++ (Qt)
~Lazy()
{
if (_instance != nullptr)
{
delete _instance;
}
}
 

delete nullptr - разрешенная операция. :)


Название: Re: C++ lazy initialization
Отправлено: ssoft от Ноябрь 30, 2017, 09:38
Вариант с лямбдой выглядит аккуратней: :)

В продолжение к посту Old
Код
C++ (Qt)
template <typename T, typename ...Param>
Lazy<T, Param...> makeLazy(Param... p) {
   return Lazy<T, Param...>(p...);
}
 
Код
C++ (Qt)
   auto p1 = makeLazy<Foo>( 10 );
   cout << p1->a() << p1->b() << endl;
 
   auto p2 = makeLazy<Foo>( 10, 20 );
   cout << p2->a() << p2->b() << endl;
 

Можно и еще усовершенствовать)

Код
C++ (Qt)
#include <functional>
#include <memory>
 
using std::shared_ptr;
using std::make_shared;
using std::function;
using std::bind;
 
template <typename T>
class Lazy
{
using pointer = shared_ptr<T>;
 
public:
typename < typename ... Param>
explicit Lazy( Param ... p ) :
m_builder( [=](){ return make_shared<T>( p... ); } )
{
}
 
pointer operator ->()
{
return ptr();
}
 
private:
pointer ptr()
{
if( !m_obj )
m_obj = m_builder();
 
return m_obj;
}
 
function<pointer ()> m_builder;
shared_ptr<T> m_obj;
public:
template < typename ... Param>
static Lazy<T> make ( Param ... p)
{
return Lazy<T>( p... );
}
};
 

тогда по коду можно будет использовать код вида

Код
C++ (Qt)
   using LazyFoo = Lazy< Foo >;
 
   LazyFoo p1 = LazyFoo::make( 10 );
   LazyFoo p2 = LazyFoo::make( 10, 20 );
   LazyFoo p3 = p1;
 

и оператор = специальным способом реализовать.


Хотелось бы узнать какая конкретно задача решается? Такая реализация Lazy имеет свои накладные расходы - копирует и запоминает параметры, необходимые для инициализации.


Название: Re: C++ lazy initialization
Отправлено: __Heaven__ от Ноябрь 30, 2017, 09:56
Код
C++ (Qt)
T* GetObject()
{
if (_instance == Q_NULLPTR)
{
_instance = _factory();
}
return _instance;
}
 
Ещё 5 копеек  :D
Код
C++ (Qt)
T* GetObject()
{
if (!_instance)
{
_instance = _factory();
}
return _instance;
}