另一个副本指针






4.78/5 (11投票s)
本文介绍如何编写和使用一个灵活的基于策略的模板拷贝指针。
引言
我决定编写一个拷贝指针类,它是一个智能指针,可以持有指针并对对象进行深拷贝。
网上有很多拷贝指针类的实现,但它们对我来说不够灵活。我发现的标准方法是通过其拷贝构造函数来复制对象,形式为new T(*pointer_to_T)
。如果我们想使用接口指针,这个解决方案就无效了。我的目标是创建一个易于使用、灵活且高效的拷贝指针类,它可以覆盖大多数情况。
在这个解决方案中,我使用了Loki库,它提供了许多有用的类,如SuperSubclass
和TypeList
。
现在,让我们从一些示例代码开始,了解CopyPtr<>
类可以做什么。
Using the Code
基本概念是像使用std::auto_ptr
一样使用该对象。
class Dummy{};
CopyPtr<Dummy> dummyPtr(new Dummy);
当dummyPtr
离开其作用域时,析构函数会销毁它。
要使它们相互拷贝,只需使用等号运算符或拷贝构造函数。
CopyPtr<Dummy> dummyPtr2(dummyPtr);
dummyPtr = dummyPtr2;
在这种情况下,CopyPtr<>
使用了Dummy
的拷贝构造函数,但您也可以使用接口拷贝。
class TestCopyable : public ICopyable
{
public:
TestCopyable(){}
virtual ICopyable* copy() const
{
// this is my custom copy
return new TestCopyable(*this);
}
protected:
TestCopyable(const TestCopyable&){ /* do something */ }
TestCopyable& operator=(const TestCopyable&) { /* do something */ return *this; }
};
// ...
CopyPtr<ICopyable> testCpy(new TestCopyable);
CopyPtr<ICopyable> testCpy2(testCpy); // it uses ICopyable::copy() function
// to clone the object
CopyPtr<TestCopyable> testCpy3(new TestCopyable);
CopyPtr<ICopyable> testCpy4(testCpy3); // it still uses the ICopyable::copy() function
testCpy3 = testCpy4; // won't compile, can't convert ICopyable to TestCopyable
testCpy4 = testCpy3; // it works, TestCopyable inherited from ICopyable
有时您想拷贝POD(Plain Old Data)类型,在这种情况下,您可以使用位拷贝。如果我们愿意,可以选择使用它;只需像这样操作
struct TestPOD
{
char c;
// and so on...
};
template<> struct SupportsBitwiseCopy<TestPOD>
{
enum { result = true };
};
// ...
CopyPtr<TestPOD> testPod(new TestPOD);
CopyPtr<TestPOD> testPod2(testPod); // using bitwise copy
如果您使用您的类型对SupportsBitwiseCopy
进行特化,您的POD的每次拷贝都将进行位拷贝。
还有另一种策略可以使用,那就是杜鹃蛋;它不进行拷贝,因为它阻止了对象的拷贝。
CopyPtr<Dummy> testDummyCopyable(new Dummy);
CopyPtr<Dummy, CopyHelper::PointerComparison,
CopyHelper::NoCopy> testDummyNoCpy(new Dummy);
testDummyCopyable = testDummyNoCopy; // invalid statement, won't compile,
// you'll get 'ERROR__Cant_Copy_Object_Ty' has incomplete type message
好的,现在您可能会问,为什么在之前的示例中要使用位拷贝,而不是拷贝构造函数?
拷贝策略的顺序是什么?如何确定这些策略的顺序?
简而言之,您可以定义新的策略,并确定它们的优先级。让我们看看幕后有哪些魔力!
技术
我使用了两种技术:基于策略的模板编程和模板元编程(TMP)。基于策略的编程的好处在 Andrei Alexandrescu 的 Modern C++ Design 中得到了很好的描述
"基于策略的类设计鼓励将具有复杂行为的类组装成许多小的类(称为策略),每个类只负责一个行为或结构方面。顾名思义,策略建立了与特定问题相关的接口。只要您遵循策略接口,就可以以各种方式实现策略。因为您可以混合搭配策略,所以可以使用一小组基本组件来实现组合行为。"
模板策略为我们提供了最大的灵活性和最有效的代码,我们可以微调我们的类以获得预期的结果。我在CopyPtr<>
、Selector<>
、InterfaceCopy<>
和BitwiseCopy<>
类中使用了这项技术。另一项技术是模板元编程;这是来自Wiki的描述
"模板元编程是一种元编程技术,其中模板由编译器用于生成临时源代码,该临时源代码由编译器与其余源代码合并然后进行编译。这些模板的输出包括编译时常量、数据结构和完整的函数。模板的使用可以被认为是编译时执行。"
我在FindPolicy<>
中使用了简单的模板元程序。
各部分结构
主要部分是CopyPtr<>
类,它将策略整合在一起。它接受两个策略(_TComparisonPolicy
、_TCopyPolicy
),这提供了灵活性。比较策略负责执行两个CopyPtr<>
之间的所需比较。您可以在两种策略之间进行选择:PointerComparison<>
和ValueComparison<>
。默认情况下,CopyPtr<>
使用PointerComparison<>
和ValueComparison<>
,它们按值比较模板类型(使用operator==()
函数)。有两种拷贝策略:一种只是执行其拷贝策略,另一种可以在编译时选择正确的拷贝策略。它们是
CopyConstructorCopy<>
这是最简单的策略;它只是调用类型的拷贝构造函数。不幸的是,我无法确定拷贝构造函数是否可访问。我没有找到任何函数可以在不引发编译错误的情况下确定拷贝构造函数的可见性。
template <class _Ty>
class CopyConstructorCopy
{
public:
enum {
isAccessible = 1 // I don't know how to check it
// in compile time
};
static _Ty* copy(const _Ty* pObject)
{
return new _Ty(*pObject);
}
static void destroy(_Ty* pObject)
{
delete pObject, pObject = 0;
}
private:
CopyConstructorCopy();
CopyConstructorCopy(const CopyConstructorCopy&);
CopyConstructorCopy& operator=(const CopyConstructorCopy&);
};
BitwiseCopy<>
逐位拷贝对象,使用memcpy()
和std::allocator
来分配内存。当然,分配器是一个模板参数,所以您可以使用自己的分配器。
template <class _Ty, template <class _Ty> class _TAllocator = std::allocator>
class BitwiseCopy
{
public:
enum {
isAccessible = SupportsBitwiseCopy<_Ty>::result
};
static _Ty* copy(const _Ty* pObject)
{
LOKI_STATIC_CHECK(!!SupportsBitwiseCopy<_Ty>::result, _BitwiseCopy_Not_Allowed_For_This_Type);
_TAllocator<_Ty> tmpAllocator;
_Ty* pTy = tmpAllocator.allocate(1);
if (!pTy)
return 0;
// for bitwise copy, we can call memcpy instead of construct
// of the allocator
memcpy(pTy, pObject, sizeof(_Ty));
return pTy;
}
static void destroy(_Ty* pObject)
{
LOKI_STATIC_CHECK(!!SupportsBitwiseCopy<_Ty>::result, _BitwiseCopy_Not_Allowed_For_This_Type);
if (!pObject) // not permitted to be null
return;
_TAllocator<_Ty> tmpAllocator;
tmpAllocator.deallocate(pObject, 1);
}
private:
BitwiseCopy();
BitwiseCopy(const BitwiseCopy&);
BitwiseCopy& operator=(const BitwiseCopy&);
};
InterfaceCopy<>
它默认使用我的ICopyable
接口(也是一个模板参数),它会调用copy()
函数,并在编译时检查对象的继承关系,所以如果您尝试拷贝一个对象但它不是InterfaceCopy<>
的子类,您将收到编译错误。
template <class _Ty, typename _TInterface = Base::ICopyable>
class InterfaceCopy
{
public:
enum {
isAccessible = Loki::SuperSubclass< _TInterface, _Ty >::value
};
static _Ty* copy(const _Ty* pObject)
{
// compile time check; _TInterface must be the parent of _Ty
LOKI_STATIC_CHECK((Loki::SuperSubclass< _TInterface, _Ty >::value),
_Ty_Must_Inherit_From_TInterface);
// if the _Ty::copy() function returns _TInterface*,
// we must cast the type back to _Ty
// we can do it safely with reinterpret_cast,
// because we check the right type before in compile time
return reinterpret_cast<_Ty*>(pObject->copy());
}
static void destroy(_Ty* pObject)
{
delete pObject, pObject = 0;
}
private:
InterfaceCopy();
InterfaceCopy(const InterfaceCopy&);
InterfaceCopy& operator=(const InterfaceCopy&);
};
NoCopy<>
杜鹃蛋。当您尝试拷贝对象时,您会收到编译错误。您可以使用它来阻止对象的拷贝。
template <class _Ty>
class NoCopy
{
public:
enum {
isAccessible = 1
};
static _Ty* copy(const _Ty* /*pObject*/)
{
// in C++11 you can't give a simple false to LOKI_STATIC_CHECK, because it will stop the
// compilation with error when you include this file. So it is necessary to add
// an expression, because it will be evaluated only when generating the specialized
// code from this template.
LOKI_STATIC_CHECK(0 == isAccessible, _Cant_Copy_Object_Ty);
return 0;
}
static void destroy(_Ty* pObject)
{
delete pObject, pObject = 0;
}
private:
NoCopy();
NoCopy(const NoCopy&);
NoCopy& operator=(const NoCopy&);
};
这些是_TCopyPolicy
的简单策略。
Selector<>
Selector<>
类帮助您为您的类选择正确的策略。您可以优先安排策略的正确顺序,它将很好地完成所有工作。选择器由两个类组成:Selector<>
是一个包装类,它提供与其他拷贝策略相同的接口,而FindPolicy<>
类则找到要使用的正确策略。它遍历策略并检查它们的isAccessible
枚举。如果此枚举为 1,我们就找到了正确的策略,将实例化一个特化模板,迭代将停止。事实上,迭代是由模板元编程解决的递归。
// selector.h
template <class _Ty>
class DefaultCopyPolicy
{
public:
typedef typename Loki::TL::MakeTypelist<
InterfaceCopy<_Ty>,
BitwiseCopy<_Ty>,
CopyConstructorCopy<_Ty>,
NoCopy<_Ty>
>::Result Result;
};
template <class _Ty, class _TCopyPolicy> class Selector;
template <class _Ty>
class Selector<_Ty, Loki::NullType>
{
public:
class No_Type_Specified_In_Type_List;
No_Type_Specified_In_Type_List BadFood;
};
/**
* \brief Automatic select the appropriate copy policy.
*/
template <class _Ty, class _TCopyPolicy = typename DefaultCopyPolicy<_Ty>::Result >
class Selector
{
protected:
typedef typename Loki::TL::TypeAt<_TCopyPolicy, 0>::Result TCurrentCopy;
typedef typename FindPolicy<_Ty, _TCopyPolicy, TCurrentCopy,
!!TCurrentCopy::isAccessible >::TCopyPolicy TCopyPolicy;
public:
/**
* \brief Make a copy from \p other using copy policy.
*/
static _Ty* copy(const _Ty* other)
{
return TCopyPolicy::copy(other);
}
/**
* \brief Destroy the object using copy policy.
*/
static void destroy(_Ty* pObject)
{
TCopyPolicy::destroy(pObject);
}
};
// findpolicy.h
template <class _Ty, class _TCopyPolicies, class _TCurrentCopy, bool isFound> class FindPolicy;
template <class _Ty, class _TCopyPolicies, class _TCurrentCopy>
class FindPolicy<_Ty, _TCopyPolicies, _TCurrentCopy, true>
{
public:
typedef _TCurrentCopy TCopyPolicy;
};
/**
* \brief Specialized template. Generates compile time error when there is no accessible copy policy in \p _TCopyPolicies type list.
*/
template <class _Ty, class _TCurrentCopy>
class FindPolicy<_Ty, Loki::NullType, _TCurrentCopy, false>
{
public:
class There_Is_No_Accessible_Copy_Policy;
There_Is_No_Accessible_Copy_Policy No_Copy_Policy_Found;
};
template <class _Ty, class _TCopyPolicies, class _TCurrentCopy>
class FindPolicy<_Ty, _TCopyPolicies, _TCurrentCopy, false>
{
public:
typedef _TCurrentCopy TCurrentCopy;
typedef typename Loki::TL::TypeAt<_TCopyPolicies, 0>::Result TNextCopy;
typedef typename FindPolicy<_Ty, typename Loki::TL::Erase<_TCopyPolicies,
TCurrentCopy>::Result,
TNextCopy, !!TCurrentCopy::isAccessible >::TCopyPolicy TCopyPolicy;
};
第一行(在findpolicy.h中)是函数的声明。最后一个模板是执行工作的“递归函数”,而中间的特化类是递归的退出点。我使用 Loki TypeList
来减少实现的 L代码量。需要注意的是,所有事情都发生在编译时,没有运行时开销。
CopyPtr<>
最后一部分是CopyPtr<>
类,它将各个部分组合在一起。它有三个模板参数:将被处理的类型、比较策略以及CopyPtr<>
的策略。
比较策略并不复杂,您可以决定在比较CopyPtr<>
对象时,类是使用指针比较还是值比较。拷贝策略可以是一个简单的策略,也可以是一个复杂的策略(例如Selector<>
)。类中没有特别之处,您可以get()
、reset()
或release()
包含的对象,或者如果愿意,可以swap()
它。swap()
默认使用std::swap
,但您也可以使用自己的 swap 函数。唯一值得注意的地方是operator=()
。有两种类型的operator=()
:一种是标准的,另一种是模板化的
template <class _Ty, template <typename> class _TComparisonPolicy, class _TCopyPolicy>
template <class _TOther, template <typename> class _TOtherComparisonPolicy,
class _TOtherCopyPolicy>
CopyPtr<_Ty, _TComparisonPolicy, _TCopyPolicy>& CopyPtr<_Ty,
_TComparisonPolicy, _TCopyPolicy>::operator=(const CopyPtr<_TOther,
_TOtherComparisonPolicy, _TOtherCopyPolicy>& other)
{
LOKI_STATIC_CHECK((Loki::SuperSubclass< _Ty, _TOther >::value),
_TOther_Must_Inherit_From_Ty);
reset();
if (other.isNull())
return *this;
// using copy policy of other object
m_pObject = _TOtherCopyPolicy::copy(other.get());
return *this;
}
此函数是为模板类型(_Ty
)的子类对象编写的。因此,它会检查继承关系,此外,它会使用其他对象的拷贝策略来拷贝对象。我们必须使用其他对象的拷贝策略,并且新指针必须与_Ty
类型兼容。完整的源代码已附上,请查看。
历史
- 2011年5月11日:初版。
- 2013年3月15日:添加了 Google 测试文件,进行了小的 bug 修复。
- 2013年3月24日:添加了更多测试并修复了一些 bug
- NoCopy 策略在 C++11 上不起作用
- 处理未找到拷贝策略或指定了错误拷贝策略的情况
- 添加了编译时检查的测试