6

Betrachten Sie eine richtlinienbasierte Smart Pointer-Klasse Ptr mit nur einer Richtlinie, die die Dereferenzierung in einem NULL-Status verhindert (irgendwie). Betrachten wir zwei Strategien dieser Art:Erhalten der Implizitheit der Konstruktion in einer richtlinienbasierten Klasse

  • NotNull
  • NoChecking

Da die NotNull Politik restriktiver ist, würden wir implizite Konvertierungen ermöglichen, wie Ptr< T, NoChecking >-Ptr< T, NotNull >, aber nicht in die entgegengesetzte Richtung. Das muss explizit für die Sicherheit sein. Bitte werfen Sie einen Blick auf die folgende Umsetzung:

#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

Der obige Code schlägt fehl, wenn implizit in beide Richtungen umzuwandeln, was bedeutet, dass std::is_convertible versagt, obwohl die Klassen kompatibel Konstrukteure haben. Die Probleme sind:

  1. Konstruktorüberladungen nicht einfach durch explizites Schlüsselwort unterscheiden kann, so müssen wir einen expliziten und impliziten Konstruktor Umwandlungsoperator (oder umgekehrt) in der Host-Klasse.
  2. Expliziter Konstruktor ist besser, weil jeder Konstruktor explizite Konstruktoren aus der Initialisierungsliste aufruft, selbst wenn er implizit ist.
  3. Der implizite Konvertierungsoperator kann keine Objekte des Richtlinientyps erstellen, da ihr Destruktor geschützt ist. Aus diesem Grund schlägt std::is_convertible fehl, wenn es nicht sollte, und deshalb können wir auch nicht so etwas wie boost::implicit_cast< const target_policy& >(*this) im Konvertierungsoperator verwenden, da dies ein temporäres Richtlinienobjekt erzeugen würde, das verboten ist.

Was die offensichtlichen Lösungen, die meiner Meinung nach nicht optimal sind:

  1. die Politik destructor öffentlich machen - und Risiko UB, wenn sie Politik der PTR * Casting * und sie zu löschen? Dies ist im angegebenen Beispiel unwahrscheinlich, aber in der realen Anwendung möglich.
  2. Machen Sie den Destruktor öffentlich und verwenden Sie geschützte Vererbung - Ich brauche die angereicherte Schnittstelle die öffentliche Vererbung bietet.

Die Frage ist:

Gibt es eine statische Prüfung auf die Existenz von impliziten Konstruktor von einem Typ zum anderen, die Objekte dieser Typen nicht schaffen?

Oder alternativ:

Wie kann ich die Informationen von impliziten Aufbau beizubehalten, wenn die Politik Konstrukteurs von der Host-Klasse Konstruktor aufrufen?


EDIT:

Ich habe erkannt, dass die zweite Frage kann leicht mit einem privaten, impliziten Liste steht Konstruktor wie folgt beantwortet werden:

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

Die Fehler sind nicht sehr lesbar und wir führen eine unnötige Anforderung an die Richtlinien ein, so dass eine Antwort auf die erste Frage vorzuziehen ist.

Antwort

4

Siehe die von N4064 genommen „perfekte Initialisierung“ -Ansatz für std::pair und std::tuple die Prüfung std::is_constructible<T, U>::value und std::is_convertible<U, T>::value

Wenn beides zutrifft beinhaltet, gibt es eine implizite Konvertierung ist, wenn nur der erste echte ist die Umwandlung explizit ist.

Die Lösung besteht darin, zwei Überladungen für den Konstruktor zu definieren, eine implizite und eine explicit, und verwenden Sie SFINAE, so dass höchstens eine Überlastung möglich ist.

+0

'std :: is_constructible :: value' und umgekehrt sind falsch. Es ist unmöglich, ein Objekt des Richtlinientyps wegen eines privaten Destruktors zu erstellen, daher bin ich mir nicht sicher, wie dies hilft. Faszinierender Trick. Ich bin sicher, ich werde es woanders benutzen. – tsuki

0

Nun, nahm das mir einige Zeit zu realisieren, aber wenn das Problem liegt in der Tatsache, dass wir keine Objekte vom Typ policy schaffen können, weil sein destructor protected ist, dann, warum wir es nicht in einem temporären Host Weiterleitungsklasse?

Ptr impliziter Umwandlungsoperator:

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

Der Trick besteht darin, die Konstrukteuren von target_policy zu target_host, zu übermitteln, so dass es von Argumenten konstruiert werden kann, dass target_policy Dose. Da sich Ptr von safety_policy ableitet, kann es implizit in (const) safety_policy&(&) konvertiert werden. Dies bedeutet, dass die Testing-Konvertierung Ptr -> target_host der Testkonstruktion target_host::target_safety(safety_policy) entspricht.


die perfekte Initialisierung Trick von Jonathan Wakely in Verbindung mit Hosting temporäre politischen Kontext verwenden wir es in der folgenden Art und Weise lösen:

#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

Verwandte Themen