boost — Как абстрактной ленивой инициализации в C ++?

При рефакторинге некоторого кода для производительности на днях мне нужен был ответ на создание переменных-членов, которые инициализируются лениво, но также предоставляют удобный, хотя и необязательный, не-лямбда-интерфейс для компиляторов, не входящих в c ++ 11.

Вот типичный шаблон для ленивых экземпляров, который я хочу абстрагировать:

if( !bInitialized )
{
value = doInitialization();
bInitialized = true;
}
return value;

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

  • разрешить явную инициализацию, как в примере выше
  • обеспечить неявный доступ к ленивому, как если бы это был базовый тип данных
  • обрабатывать неинициализированный доступ (throws), в случае, если я облажался при явной инициализации (например, забыл назначить значение)
  • также поддерживает реальную ленивую инициализацию через функцию, функтор и / или лямбду
  • разрешить ручную инициализацию через указатель на содержащееся значение (например, при вызове Win32 API)
  • разрешить переназначение значения; в большинстве случаев рассматривайте ленивый как основной тип данных.

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

4

Решение

Вот мое решение, включая набор модульных тестов, созданный на основе библиотеки модульных тестов Microsoft.

Он обрабатывает требования OP — единственный класс Lazy, который обеспечивает:

  • неявная инициализация «при первом использовании» через функцию обратного вызова, функтор или лямбду
  • или явная инициализация через присваивание
  • или явная инициализация через указатель на внутреннюю структуру данных
  • он выдаст исключение, если будет предпринята попытка получить доступ к неинициализированному ленивому; например, чтобы указать, что вы забыли явно инициализировать ленивый, и не существует неявного инициализатора

Кроме того,

  • он может работать с типами данных, которые не имеют конструктора по умолчанию
  • а также обрабатывает ленивое управление ресурсами с помощью необязательного обратного вызова деинициализации (закрытия)

Во-первых, пример использования:

class MyClass
{
Lazy<int> m_test;
Lazy<int> m_testViaInitializer;
Lazy<int> m_testViaInitializationOfPointer;

public:
MyClass::MyClass()
: m_testViaInitializer( intFactory )
{
}

int MyClass::lazy_ImplicitInitialization()
{
return m_testViaInitializer;
}

int MyClass::lazy_ExplicitInitialization()
{
if( !m_test.isInitialized() )
{
m_test = 42;
}
return m_test;
}

int MyClass::lazy_InitializationViaPointer()
{
if( !m_test.isInitialized() )
{
intFactoryViaPointer( & m_testViaInitializationOfPointer );
m_testViaInitializationOfPointer.forceInitialized();
}
return m_testViaInitializationOfPointer;
}

Lazy<FILE*> MyClass::lazy_ResourceManagement()
{
Lazy<FILE*> lazyFile(
/*open*/  []() { return fopen("test.txt", "w"); },
/*close*/ [](FILE*& h) { fclose(h); } );

return lazyFile;
}

private:
static int intFactory()
{
return 42;
}

static void intFactoryViaPointer( int * v )
{
*v = 42;
}

};

И вот код. Эта версия использует библиотеку stdc ++ 11, но может быть легко преобразована для использования boost.

Lazy.hpp

#pragma once

#include <functional>
#include <stdexcept>

// Exception thrown on attempt to access an uninitialized Lazy
struct uninitialized_lazy_exception : public std::runtime_error
{
uninitialized_lazy_exception()
:std::runtime_error( "uninitialized lazy value" )
{}
};

template<typename T>
struct Lazy
{
// Default constructor
Lazy()
:m_bInitialized(false)
,m_initializer(DefaultInitializer)
,m_deinitializer(DefaultDeinitializer)
{
}

// Construct with initializer and optional deinitializer functor
Lazy( std::function<T(void)> initializer, std::function<void(T&)> deinitializer = DefaultDeinitializer )
:m_bInitialized(false)
,m_initializer(initializer)
,m_deinitializer(deinitializer)
{
}

// Copy constructor
Lazy( const Lazy& o )
:m_bInitialized(false)
,m_initializer(o.m_initializer)
,m_deinitializer(o.m_deinitializer)
{
if( o.m_bInitialized )
construct( *o.valuePtr() );
}

// Assign from Lazy<T>
Lazy& operator=( const Lazy<T>& o )
{
destroy();
m_initializer   = o.m_initializer;
m_deinitializer = o.m_deinitializer;
if( o.m_bInitialized )
construct(*o.valuePtr());
return *this;
}

// Construct from T
Lazy( const T& v )
:m_bInitialized(false)
,m_initializer(DefaultInitializer)
,m_deinitializer(DefaultDeinitializer)
{
construct(v);
}

// Assign from T
T& operator=(const T& value )
{
construct(value);
return *valuePtr();
}

// Destruct and deinitialize
~Lazy()
{
destroy();
}

// Answer true if initialized, either implicitly via function or explicitly via assignment
bool isInitialized() const
{
return m_bInitialized;
}

// Force initialization, if not already done, and answer with the value
// Throws exception if not implicitly or explicitly initialized
T& force() const
{
if( !m_bInitialized )
{
construct(m_initializer());
}
return *valuePtr();
}

// Implicitly force initialization and answer with value
operator T&() const
{
return force();
}

// Get pointer to storage of T, regardless of initialized state
T* operator &() const
{
return valuePtr();
}

// Force initialization state to true, e.g. if value initialized directly via pointer
void forceInitialized()
{
m_bInitialized = true;
}

private:
mutable char            m_value[sizeof(T)];
mutable bool            m_bInitialized;
std::function<T(void)>  m_initializer;
std::function<void(T&)> m_deinitializer;

// Get pointer to storage of T
T* valuePtr() const
{
return static_cast<T*>( static_cast<void*>( &m_value ) );
}

// Call copy constructor for T.  Deinitialize self first, if necessary.
void construct(const T& value) const
{
destroy();
new (valuePtr()) T(value);
m_bInitialized = true;
}

// If initialized, call deinitializer and then destructor for T
void destroy() const
{
if( m_bInitialized )
{
m_deinitializer(*valuePtr());
valuePtr()->~T();
m_bInitialized = false;
}
}

// Inititializer if none specified; throw exception on attempt to access uninitialized lazy
static T DefaultInitializer()
{
throw uninitialized_lazy_exception();
}

// Deinitialize if none specified; does nothing
static void DefaultDeinitializer(T&)
{
}
};

test_Lazy.cpp

#include "stdafx.h"#include "CppUnitTest.h"
#include "Lazy.hpp"#include <memory>
#include <string>

using namespace std;

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace Lazy_Test
{
TEST_CLASS(test_Lazy)
{
public:
TEST_METHOD(Lazy_ReturnsValueOnForce)
{
const Lazy<int> test( []()
{
return 42;
} );
Assert::AreEqual( false, test.isInitialized() );
Assert::AreEqual( 42, test.force() );
Assert::AreEqual( true, test.isInitialized() );
}

TEST_METHOD(Lazy_ManualInitialization)
{
Lazy<int> test;
Assert::AreEqual( false, test.isInitialized() );

if( !test.isInitialized() )
{
test = 42;
}

Assert::AreEqual( 42, (int)test );
Assert::AreEqual( 42, test.force() );
Assert::AreEqual( true, test.isInitialized() );
}

TEST_METHOD(UninitializedLazy_ThrowsExceptionOnForce)
{
const Lazy<int> test;
Assert::AreEqual( false, test.isInitialized() );
Assert::ExpectException<uninitialized_lazy_exception>( [&test]() { test.force(); } );
}

TEST_METHOD(Lazy_ManualInitializationViaPointer)
{
Lazy<int> test;
Assert::AreEqual( false, test.isInitialized() );

if( !test.isInitialized() )
{
int* pTest = &test;
*pTest = 42;
test.forceInitialized();
}

Assert::AreEqual( true, test.isInitialized() );
Assert::AreEqual( 42, (int)test );
Assert::AreEqual( 42, test.force() );
}

TEST_METHOD(Lazy_DoesResourceDeinitialization)
{
typedef unsigned HANDLE;
bool bIsOpen = false;   // side-effect for unit testing

function<HANDLE()> openLambda = [&bIsOpen]() {
bIsOpen = true;
return 12345;
};

function<void(HANDLE&)> closeLambda = [&bIsOpen](HANDLE& h) {
bIsOpen = false;
// e.g.., Close(h);
};

{
Lazy<HANDLE> lazyHandle( openLambda, closeLambda );
Assert::AreEqual( false, bIsOpen );

HANDLE h = lazyHandle;
Assert::AreEqual( true, bIsOpen );
Assert::AreEqual( (HANDLE)12345, h );
}
Assert::AreEqual( false, bIsOpen );
}

TEST_METHOD(Lazy_CopiesCorrectly)
{
const Lazy<int> a( []() { return 42; } );
Lazy<int> b;
Lazy<int> c = (b = a);

Assert::AreEqual( false, a.isInitialized() );
Assert::AreEqual( false, b.isInitialized() );
Assert::AreEqual( false, c.isInitialized() );

Assert::AreEqual( 42, a.force() );
Assert::AreEqual( true, a.isInitialized() );
Assert::AreEqual( false, b.isInitialized() );
Assert::AreEqual( false, c.isInitialized() );

Assert::AreEqual( 42, b.force() );
Assert::AreEqual( false, c.isInitialized() );
Assert::AreEqual( true, b.isInitialized() );
Assert::AreEqual( true, a.isInitialized() );

Assert::AreEqual( 42, c.force() );
Assert::AreEqual( true, c.isInitialized() );
Assert::AreEqual( true, b.isInitialized() );
Assert::AreEqual( true, a.isInitialized() );

}

TEST_METHOD(Lazy_HandlesAssignment)
{
const Lazy<int> a ([]() { return 42; } );
Lazy<int> b;

Assert::AreEqual( false, a.isInitialized() );
Assert::AreEqual( false, b.isInitialized() );

Assert::AreEqual( 42, a.force() );
Assert::AreEqual( true, a.isInitialized() );
Assert::AreEqual( false, b.isInitialized() );

b = 43;
Assert::AreEqual( true, b.isInitialized() );
Assert::AreEqual( 43, b.force() );

b = a;
Assert::AreEqual( true, b.isInitialized() );
Assert::AreEqual( 42, b.force() );
}

TEST_METHOD(Lazy_HandlesNonTrivialClass)
{
Lazy<string>    a( []() { return "fee"; } );
Lazy<string>    b( string("fie") );
Lazy<string>    c; c = "foe";
Lazy<string>    d(c);

Assert::AreEqual( string("fee"), (string)a );
Assert::AreEqual( string("fie"), (string)b );
Assert::AreEqual( string("foe"), (string)c );
Assert::AreEqual( string("foe"), (string)d );

d = "fum";
Assert::AreEqual( string("fum"), (string)d );

Assert::AreEqual( (size_t)3, a.force().length() );
Assert::AreEqual( (size_t)3, b.force().length() );
Assert::AreEqual( (size_t)3, c.force().length() );
Assert::AreEqual( (size_t)3, d.force().length() );

}

struct NoDefaultConstructor
{
NoDefaultConstructor(bool v) : m_state(v)
{}
NoDefaultConstructor(const NoDefaultConstructor& o) : m_state(o.m_state)
{}
NoDefaultConstructor& operator=(const NoDefaultConstructor& o)
{
m_state=o.m_state;
return *this;
}
bool operator !()
{
return !m_state;
}
private:
bool m_state;
};

TEST_METHOD(Lazy_HandlesNoDefaultConstructor)
{
Lazy<NoDefaultConstructor> v( []() { return NoDefaultConstructor(true); } );
Assert::IsTrue( !!v.force() );
}
};
}
4

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]