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;
}
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:
- 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.
- Expliziter Konstruktor ist besser, weil jeder Konstruktor explizite Konstruktoren aus der Initialisierungsliste aufruft, selbst wenn er implizit ist.
- 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 wieboost::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:
- 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.
- 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.
'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