6

考慮一個基於策略的智能指針類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; 
} 

的錯誤是不太可讀,我們對這些政策提出了不必要的要求,所以對第一個問題的回答更可取。

回答

4

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

0

好了,我花了一些時間來實現,但是如果問題在於這樣一個事實,我們不能創造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