这是我的解决方案,包括基于 Microsoft 原生单元测试库构建的单元测试套件。
它处理 OP 的要求——一个单一的 Lazy 类,它提供:
- 通过函数回调、仿函数或 lambda 进行隐式“首次使用”初始化
- 或者,通过赋值显式初始化
- 或者,通过指向内部数据结构的指针进行显式初始化
- 如果尝试访问未初始化的惰性对象,它将抛出异常;例如,表明您忘记显式初始化惰性并且没有隐式初始化程序
加,
- 它可以处理没有默认构造函数的数据类型
- 并且还通过可选的去初始化(关闭)回调处理惰性资源管理
首先,一个使用示例:
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。
懒惰的.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() );
}
};
}