考虑一个基于策略的智能指针类 Ptr,它只有一个策略可以防止在 NULL 状态下(以某种方式)取消引用它。让我们考虑 2 种此类策略:
NotNull
NoChecking
由于该NotNull
策略更具限制性,我们希望允许从Ptr< T, NoChecking >
到的隐式转换Ptr< T, NotNull >
,但不允许相反方向的转换。为了安全起见,那个必须是明确的。请看下面的实现:
#include <iostream>
#include <type_traits>
#include <typeinfo>
struct NoChecking;
struct NotNull;
struct NoChecking{
NoChecking() = default;
NoChecking( const NoChecking&) = default;
explicit NoChecking( const NotNull& )
{ std::cout << "explicit conversion constructor of NoChecking" << std::endl; }
protected:
~NoChecking() {} //defaulting the destructor in GCC 4.8.1 makes it public somehow :o
};
struct NotNull{
NotNull() = default;
NotNull( const NotNull&) = default;
NotNull( const NoChecking& )
{ std::cout << "explicit conversion constructor of NotNull" << std::endl; }
protected:
~NotNull() {}
};
template<
typename T,
class safety_policy
> class Ptr
: public safety_policy
{
private:
T* pointee_;
public:
template <
typename f_T,
class f_safety_policy
> friend class Ptr; //we need to access the pointee_ of other policies when converting
//so we befriend all specializations of Ptr
//implicit conversion operator
template<
class target_safety
> operator Ptr<T, target_safety>() const {
std::cout << "implicit conversion operator of " << typeid( *this ).name() << std::endl;
static_assert( std::is_convertible<const safety_policy&, const target_safety&>::value,
//What is the condition to check? This requires constructibility
"Safety policy of *this is not implicitly convertible to target's safety policy." );
//calls the explicit conversion constructor of the target type
return Ptr< T, target_safety >( *this );
}
//explicit conversion constructor
template<
class target_safety
> explicit Ptr( const Ptr<T, target_safety>& other )
: safety_policy( other ), //this is an explicit constructor call and will call explicit constructors when we make Ptr() constructor implicit!
pointee_( other.pointee_ )
{ std::cout << "explicit Ptr constructor of " << typeid( *this ).name() << std::endl; }
Ptr() = default;
};
//also binds to temporaries from conversion operators
void test_noChecking( const Ptr< int, NoChecking >& )
{ }
void test_notNull( const Ptr< int, NotNull >& )
{ }
int main()
{
Ptr< int, NotNull > notNullPtr; //enforcing not null value not implemented for clarity
Ptr< int, NoChecking > fastPtr( notNullPtr ); //OK - calling explicit constructor and NotNull is explicitly convertible to NoChecking
test_notNull ( fastPtr ); //should be OK - NoChecking is implictly convertible to NotNull
test_noChecking ( notNullPtr ); //should be ERROR - NotNull is explicitly convertible to NoChecking
return 0;
}
上面的代码在双向隐式转换时失败,这意味着std::is_convertible
即使类具有兼容的构造函数也会失败。问题是:
- 构造函数重载不能仅仅通过显式关键字来区分,因此我们需要在宿主类中显式构造函数和隐式转换运算符(反之亦然)。
- 显式构造函数更好,因为任何构造函数都会从初始化列表中调用显式构造函数,即使它本身是隐式的。
- 隐式转换运算符无法创建策略类型的对象,因为它们的析构函数受到保护。这就是为什么
std::is_convertible
它不应该失败的原因,这也是为什么我们不能boost::implicit_cast< const target_policy& >( *this )
在转换运算符中使用类似的东西,因为它会创建一个临时的策略对象,这是被禁止的。
至于我认为不是最优的明显解决方案:
- 将策略析构函数公开- 并在将 Ptr* 转换为 policy* 并删除它时冒 UB 风险?这在所提供的示例中不太可能,但在实际应用中是可能的。
- 将析构函数设为公共并使用受保护的继承——我需要公共继承提供的丰富接口。
问题是:
是否存在从一种类型到另一种不创建这些类型的对象的隐式构造函数的静态测试?
或者:
从宿主类的构造函数调用策略构造函数时,如何保留隐式构造的信息?
编辑:
我刚刚意识到第二个问题可以很容易地用一个私有的、隐式标记的构造函数来回答,如下所示:
#include <iostream>
#include <type_traits>
#include <typeinfo>
struct implicit_flag {};
struct NoChecking;
struct NotNull;
struct NoChecking{
NoChecking() = default;
NoChecking( const NoChecking&) = default;
protected:
explicit NoChecking( const NotNull& )
{ std::cout << "explicit conversion constructor of NoChecking" << std::endl; }
~NoChecking() {} //defaulting the destructor in GCC 4.8.1 makes it public somehow :o
};
struct NotNull{
NotNull() = default;
NotNull( const NotNull&) = default;
protected:
NotNull( implicit_flag, const NoChecking& )
{ std::cout << "explicit conversion constructor of NotNull" << std::endl; }
~NotNull() {}
};
template<
typename T,
class safety_policy
> class Ptr
: public safety_policy
{
private:
T* pointee_;
public:
template <
typename f_T,
class f_safety_policy
> friend class Ptr; //we need to access the pointee_ of other policies when converting
//so we befriend all specializations of Ptr
//implicit conversion operator
template<
class target_safety
> operator Ptr<T, target_safety>() const {
std::cout << "implicit conversion operator of " << typeid( *this ).name() << std::endl;
/*static_assert( std::is_convertible<const safety_policy&, const target_safety&>::value, //What is the condition to check? This requires constructibility
"Safety policy of *this is not implicitly convertible to target's safety policy." );*/
//calls the explicit conversion constructor of the target type
return Ptr< T, target_safety >( implicit_flag(), *this );
}
//explicit conversion constructor
template<
class target_safety
> explicit Ptr( const Ptr<T, target_safety>& other )
: safety_policy( other ), //this is an explicit constructor call and will not preserve the implicity of conversion!
pointee_( other.pointee_ )
{ std::cout << "explicit Ptr constructor of " << typeid( *this ).name() << std::endl; }
private:
//internal implicit-flagged constructor caller that is called from implicit conversion operator
template<
class target_safety
> Ptr( implicit_flag implicit, const Ptr<T, target_safety>& other )
: safety_policy( implicit, other ), //this constructor is required in the policy
pointee_( other.pointee_ )
{ std::cout << "explicit Ptr constructor of " << typeid( *this ).name() << std::endl; }
public:
Ptr() = default;
};
//also binds to temporaries from conversion operators
void test_noChecking( const Ptr< int, NoChecking >& )
{ }
void test_notNull( const Ptr< int, NotNull >& )
{ }
int main()
{
Ptr< int, NotNull > notNullPtr; //enforcing not null value not implemented for clarity
Ptr< int, NoChecking > fastPtr( notNullPtr ); //OK - calling explicit constructor and NotNull is explicitly convertible to NoChecking
test_notNull ( fastPtr ); //should be OK - NoChecking is implictly convertible to NotNull
test_noChecking ( notNullPtr ); //should be ERROR - NotNull is explicitly convertible to NoChecking
return 0;
}
然而,这些错误不是很可读,并且我们对策略引入了不必要的要求,因此对第一个问题的回答更可取。