在基于策略的类中保留构造的隐含性

问题描述:

考虑一个基于策略的智能指针类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; 
} 

Live example

的代码时,在两个方向上,这意味着std::is_convertible失败,即使类具有兼容的构造函数隐式转换上面的失败。问题是:

  1. 构造函数重载不能简单地通过显式关键字不同,所以我们需要在宿主类中显式构造函数和隐式转换运算符(反之亦然)。
  2. 显式构造函数更好,因为任何构造函数都会从初始化列表中调用显式构造函数,即使它本身是隐式的。
  3. 隐式转换运算符不能创建策略类型的对象,因为它们的析构函数受到保护。这就是为什么std::is_convertible不应该失败,这也是为什么我们不能在转换运算符中使用类似boost::implicit_cast< const target_policy& >(*this)的原因,因为它会创建一个临时策略对象,这是禁止的。

至于明显的解决方案不是最优的,我认为:

  1. 使策略析构函数公共 - 和风险UB铸造时PTR *政策*和删除呢?在提供的例子中这不太可能,但是在现实世界的应用中是可能的。
  2. 使析构函数公开并使用受保护的继承 - 我需要公共继承提供的丰富接口。

的问题是:

是否有另一个不创建这些类型的对象静态测试的隐式构造函数的存在从一种类型?

或者:

从主机类的构造函数的构造函数调用的政策时,如何保持隐建设的信息?


编辑:

我只是意识到第二个问题可以用这样一个私人的,隐含的国旗的构造很容易回答:但是

#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; 
} 

的错误是不太可读,我们对这些政策提出了不必要的要求,所以对第一个问题的回答更可取。

N4064std::pairstd::tuple所采取的“完美初始化”的做法,包括:测试std::is_constructible<T, U>::valuestd::is_convertible<U, T>::value

如果两个都是真的,有一个隐式转换,如果只有第一个是真正的转换是明确的。

解决方案是为构造函数定义两个重载,一个是隐式的,一个是explicit,并使用SFINAE,以便至多有一个重载是可行的。

+0

'的std :: is_constructible :: value',反之亦然。由于私有析构函数,构造策略类型的对象是不可能的,所以我不确定这有什么帮助。迷人的把戏,但。我相信我会在别的地方使用它。 – tsuki 2014-08-27 11:44:43

好了,我花了一些时间来实现,但是如果问题在于这样一个事实,我们不能创造policy类型的对象,因为它的析构函数为protected,那么我们为什么不以暂时托管它转发课程?

PTR的隐式转换算子:

template< 
    class target_safety 
    > operator Ptr<T, target_safety>() const { 
    std::cout << "implicit conversion operator of " << typeid(*this).name() << std::endl; 

    struct target_host : target_safety { using target_safety::target_safety; }; 

    static_assert(std::is_convertible<Ptr, target_host>::value, 
        //Now this works, because target_host is constructible! 
        "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); 
    } 

Live demonstration

诀窍是的target_policy构造函数转发到target_host,因此它可以从参数构成,即target_policy即可。由于Ptr源自safety_policy,因此它可以隐式转换为(const) safety_policy&(&)。这意味着测试转换Ptr -> target_host相当于测试施工target_host::target_safety(safety_policy)


使用由Jonathan Wakely与临时政策托管,我们可以通过以下方式解决它结合提供了完善的初始化招:

#include <iostream> 
#include <type_traits> 
#include <typeinfo> 

template< typename Policy > 
struct policy_host_ 
: Policy 
{ 
    using Policy::Policy; 
}; 

template< typename Source, typename Target > 
struct is_implicitly_convertible 
: std::integral_constant< 
    bool 
    , std::is_constructible< policy_host_<Target>, policy_host_<Source> >::value && 
    std::is_convertible< policy_host_<Source>,policy_host_<Target> >::value 
    > 
{ }; 

template< typename Source, typename Target > 
struct is_explicitly_convertible 
: std::integral_constant< 
    bool 
    , std::is_constructible< policy_host_<Target>, policy_host_<Source> >::value && 
    !std::is_convertible< policy_host_<Source>,policy_host_<Target> >::value 
    > 
{ }; 

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 

    template< 
    class target_safety, 
    typename std::enable_if< 
     is_implicitly_convertible< target_safety, safety_policy >::value 
    , bool>::type = false 
    > Ptr(const Ptr<T, target_safety>& other) 
    : safety_policy(other), 
    pointee_(other.pointee_) 
    { std::cout << "implicit Ptr constructor of " << typeid(*this).name() << std::endl; } 

    template< 
    class target_safety, 
    typename std::enable_if< 
     is_explicitly_convertible< target_safety, safety_policy >::value 
    , bool>::type = false 
    > 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; } 

    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; 
} 

Live demo