Considerare una classe di puntatore intelligente basata su policy Ptr con una sola politica che impedirà il dereferenziamento dello stesso in uno stato NULL (in qualche modo). Prendiamo in considerazione 2 politiche di questo tipo:Come preservare l'implicità della costruzione in una classe basata su criteri
NotNull
NoChecking
Poiché la politica NotNull
è più restrittiva, vorremmo consentire conversioni implicite Ptr< T, NoChecking >
-Ptr< T, NotNull >
, ma non in senso opposto direzione. Quello deve essere esplicito per sicurezza. Si prega di dare un'occhiata al seguente implementazione:
#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;
}
Il codice di cui sopra non è riuscita quando implicitamente la conversione in entrambe le direzioni, il che significa che std::is_convertible
fallisce anche se le classi hanno costruttori compatibili. I problemi sono:
- Gli overload di costruttore non possono differire semplicemente dalla parola chiave esplicita, quindi abbiamo bisogno di un costruttore esplicito e di un operatore di conversione implicita (o viceversa) nella classe host.
- Il costruttore esplicito è migliore, poiché qualsiasi costruttore chiamerà costruttori espliciti dall'elenco di inizializzazione anche se è implicito stesso.
- L'operatore di conversione implicita non può creare oggetti di tipo di politica perché il loro distruttore è protetto. Questo è il motivo per cui
std::is_convertible
fallisce quando non dovrebbe, e questo è anche il motivo per cui non possiamo usare qualcosa comeboost::implicit_cast< const target_policy& >(*this)
nell'operatore di conversione, poiché creerebbe un oggetto di politica temporaneo, che è vietato.
Per quanto riguarda le soluzioni ovvie che non sono ottimali, a mio parere:
- Fai il distruttore politica pubblica - e UB rischio durante il lancio il Ptr * a * la politica e l'eliminazione di esso? Questo è improbabile nell'esempio fornito, ma è possibile nell'applicazione reale.
- Rendi pubblico il distruttore e utilizza l'ereditarietà protetta - Ho bisogno dell'interfaccia arricchita fornita dall'ereditarietà pubblica.
La domanda è:
Esiste una prova statica per l'esistenza del costruttore implicita da un tipo ad un altro che non crea oggetti di questo tipo?
O in alternativa:
Come si conservano le informazioni di costruzione implicita quando si chiama costruttori dalla classe host le politiche costruttore?
EDIT:
Ho appena realizzato che la seconda questione può essere facilmente risolta con un costruttore di implicita battente bandiera privato in questo modo:
#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;
}
Gli errori però sono non molto leggibile e introduciamo un requisito non necessario alle politiche, quindi una risposta alla prima domanda è più preferibile.
'std :: is_constructible :: value' e viceversa sono falsi. È impossibile costruire un oggetto di tipo di politica a causa del distruttore privato, quindi non sono sicuro di come ciò possa essere d'aiuto. Trucco affascinante però. Sono sicuro che lo userò da qualche altra parte. –
tsuki