2010-03-29 18 views
24

Estoy creando una capa de acceso a la base de datos en C++ nativo, y estoy buscando formas de admitir valores NULL. Aquí es lo que tengo hasta ahora:Valores anulables en C++

class CNullValue 
{ 
public: 
    static CNullValue Null() 
    { 
     static CNullValue nv; 

     return nv; 
    } 
}; 

template<class T> 
class CNullableT 
{ 
public: 
    CNullableT(CNullValue &v) : m_Value(T()), m_IsNull(true) 
    { 
    } 

    CNullableT(T value) : m_Value(value), m_IsNull(false) 
    { 
    } 

    bool IsNull() 
    { 
     return m_IsNull; 
    } 

    T GetValue() 
    { 
     return m_Value; 
    } 

private: 
    T m_Value; 
    bool m_IsNull; 
}; 

Así es como voy a tener que definir funciones:

void StoredProc(int i, CNullableT<int> j) 
{ 
    ...connect to database 
    ...if j.IsNull pass null to database etc 
} 

Y llamarlo así:

sp.StoredProc(1, 2); 

o

sp.StoredProc(3, CNullValue::Null()); 

Me preguntaba si había una mejor mucho más que esto. En particular, no me gusta el objeto tipo singleton de CNullValue con la estática. preferiría simplemente hacer

sp.StoredProc(3, CNullValue); 

o algo similar. ¿Cómo otros resuelven este problema?

Respuesta

27

Boost.Optional probablemente hace lo que necesita.

boost::none toma el lugar de su CNullValue::Null(). Dado que es una llamada de función de valor en lugar de miembro, puede hacer using boost::none; si lo desea, para abreviar. Tiene una conversión a bool en lugar de IsNull, y en lugar de operator*GetValue, por lo que haría:

void writeToDB(boost::optional<int> optional_int) { 
    if (optional_int) { 
     pass *optional_int to database; 
    } else { 
     pass null to database; 
    } 
} 

Pero lo que he llegado con es esencialmente el mismo diseño, creo.

+0

Especialmente si se considera que es equivalente al valor incrustado en cuanto al rendimiento, ya que no usan la asignación de montón. –

+0

Gracias, mirando esta biblioteca ahora ... – DanDan

+0

Acabo de probarlo. Es perfecto. – DanDan

3

Reemplace IsNull con HasValue y tiene el tipo .NET Nullable.

Por supuesto .. esto es C++. ¿Por qué no simplemente usar un puntero a un tipo "primitivo"?

+8

Los apuntadores tienen una semántica de copia diferente. Si copia un puntero no nulo, entonces la copia se refiere al mismo objeto. Si copia este CNullableT, la copia tiene su propia instancia del valor. En algunas situaciones, quiere una y otra parte, pero eso es un problema independiente de si desea que el rango de valores sea "cualquier T, o ninguno". Entonces, usar un puntero para un valor opcional trae algo de equipaje. –

+1

Yo sólo estaba tratando de hacer el limpiador de interfaz para la mayoría de los casos, por lo que se puede hacer StoredProcedcure (1, 2) en lugar de int p = 2; StoredProcedcure (1, &p); Sí, se utilizó el tipo .NET Nullable como inspiración :) – DanDan

+0

@Steve Jessop: excelente punto que no había considerado. – Randolpho

12

EDIT: Se ha mejorado con la excepción del lanzamiento en el valor "nulo". Más correcciones

Si impulso no es una opción, en C++ 11 también puede tomar ventaja de nullptr y la nullptr_t typedef para crear un Nullable<T> con más o menos misma semántica que uno .NET.

#pragma once 

#include <cstddef> 
#include <stdexcept> 

template <typename T> 
class Nullable final 
{ 
public: 
    Nullable(); 
    Nullable(const T &value); 
    Nullable(nullptr_t nullpointer); 
    const Nullable<T> & operator=(const Nullable<T> &value); 
    const Nullable<T> & operator=(const T &value); 
    const Nullable<T> & operator=(nullptr_t nullpointer); 
    bool HasValue() const; 
    const T & GetValueOrDefault() const; 
    const T & GetValueOrDefault(const T &default) const; 
    bool TryGetValue(T &value) const; 

public: 
    class NullableValue final 
    { 
    public: 
     friend class Nullable; 

    private: 
     NullableValue(); 
     NullableValue(const T &value); 

    public: 
     NullableValue & operator=(const NullableValue &) = delete; 
     operator const T &() const; 
     const T & operator*() const; 
     const T * operator&() const; 

     // https://stackoverflow.com/questions/42183631/inability-to-overload-dot-operator-in-c 
     const T * operator->() const; 

    public: 
     template <typename T2> 
     friend bool operator==(const Nullable<T2> &op1, const Nullable<T2> &op2); 

     template <typename T2> 
     friend bool operator==(const Nullable<T2> &op, const T2 &value); 

     template <typename T2> 
     friend bool operator==(const T2 &value, const Nullable<T2> &op); 

     template <typename T2> 
     friend bool operator==(const Nullable<T2> &op, nullptr_t nullpointer); 

     template <typename T2> 
     friend bool operator!=(const Nullable<T2> &op1, const Nullable<T2> &op2); 

     template <typename T2> 
     friend bool operator!=(const Nullable<T2> &op, const T2 &value); 

     template <typename T2> 
     friend bool operator!=(const T2 &value, const Nullable<T2> &op); 

     template <typename T2> 
     friend bool operator==(nullptr_t nullpointer, const Nullable<T2> &op); 

     template <typename T2> 
     friend bool operator!=(const Nullable<T2> &op, nullptr_t nullpointer); 

     template <typename T2> 
     friend bool operator!=(nullptr_t nullpointer, const Nullable<T2> &op); 

    private: 
     void checkHasValue() const; 

    private: 
     bool m_hasValue; 
     T m_value; 
    }; 

public: 
    NullableValue Value; 
}; 

template <typename T> 
Nullable<T>::NullableValue::NullableValue() 
    : m_hasValue(false), m_value(T()) { } 

template <typename T> 
Nullable<T>::NullableValue::NullableValue(const T &value) 
    : m_hasValue(true), m_value(value) { } 

template <typename T> 
Nullable<T>::NullableValue::operator const T &() const 
{ 
    checkHasValue(); 
    return m_value; 
} 

template <typename T> 
const T & Nullable<T>::NullableValue::operator*() const 
{ 
    checkHasValue(); 
    return m_value; 
} 

template <typename T> 
const T * Nullable<T>::NullableValue::operator&() const 
{ 
    checkHasValue(); 
    return &m_value; 
} 

template <typename T> 
const T * Nullable<T>::NullableValue::operator->() const 
{ 
    checkHasValue(); 
    return &m_value; 
} 

template <typename T> 
void Nullable<T>::NullableValue::checkHasValue() const 
{ 
    if (!m_hasValue) 
     throw std::exception("Nullable object must have a value"); 
} 

template <typename T> 
bool Nullable<T>::HasValue() const { return Value.m_hasValue; } 

template <typename T> 
const T & Nullable<T>::GetValueOrDefault() const 
{ 
    return Value.m_value; 
} 

template <typename T> 
const T & Nullable<T>::GetValueOrDefault(const T &default) const 
{ 
    if (Value.m_hasValue) 
     return Value.m_value; 
    else 
     return default; 
} 

template <typename T> 
bool Nullable<T>::TryGetValue(T &value) const 
{ 
    value = Value.m_value; 
    return Value.m_hasValue; 
} 

template <typename T> 
Nullable<T>::Nullable() { } 

template <typename T> 
Nullable<T>::Nullable(nullptr_t nullpointer) { (void)nullpointer; } 

template <typename T> 
Nullable<T>::Nullable(const T &value) 
    : Value(value) { } 

template <typename T2> 
bool operator==(const Nullable<T2> &op1, const Nullable<T2> &op2) 
{ 
    if (op1.Value.m_hasValue != op2.Value.m_hasValue) 
     return false; 

    if (op1.Value.m_hasValue) 
     return op1.Value.m_value == op2.Value.m_value; 
    else 
     return true; 
} 

template <typename T2> 
bool operator==(const Nullable<T2> &op, const T2 &value) 
{ 
    if (!op.Value.m_hasValue) 
     return false; 

    return op.Value.m_value == value; 
} 

template <typename T2> 
bool operator==(const T2 &value, const Nullable<T2> &op) 
{ 
    if (!op.Value.m_hasValue) 
     return false; 

    return op.Value.m_value == value; 
} 

template <typename T2> 
bool operator==(const Nullable<T2> &op, nullptr_t nullpointer) 
{ 
    (void)nullpointer; 
    return !op.Value.m_hasValue; 
} 

template <typename T2> 
bool operator==(nullptr_t nullpointer, const Nullable<T2> &op) 
{ 
    (void)nullpointer; 
    return !op.Value.m_hasValue; 
} 

template <typename T2> 
bool operator!=(const Nullable<T2> &op1, const Nullable<T2> &op2) 
{ 
    if (op1.Value.m_hasValue != op2.Value.m_hasValue) 
     return true; 

    if (op1.Value.m_hasValue) 
     return op1.Value.m_value != op2.Value.m_value; 
    else 
     return false; 
} 

template <typename T2> 
bool operator!=(const Nullable<T2> &op, const T2 &value) 
{ 
    if (!op.Value.m_hasValue) 
     return true; 

    return op.Value.m_value != value; 
} 

template <typename T2> 
bool operator!=(const T2 &value, const Nullable<T2> &op) 
{ 
    if (!op.Value.m_hasValue) 
     return false; 

    return op.Value.m_value != value; 
} 

template <typename T2> 
bool operator!=(const Nullable<T2> &op, nullptr_t nullpointer) 
{ 
    (void)nullpointer; 
    return op.Value.m_hasValue; 
} 

template <typename T2> 
bool operator!=(nullptr_t nullpointer, const Nullable<T2> &op) 
{ 
    (void)nullpointer; 
    return op.Value.m_hasValue; 
} 

template <typename T> 
const Nullable<T> & Nullable<T>::operator=(const Nullable<T> &value) 
{ 
    Value.m_hasValue = value.Value.m_hasValue; 
    Value.m_value = value.Value.m_value; 
    return *this; 
} 

template <typename T> 
const Nullable<T> & Nullable<T>::operator=(const T &value) 
{ 
    Value.m_hasValue = true; 
    Value.m_value = value; 
    return *this; 
} 

template <typename T> 
const Nullable<T> & Nullable<T>::operator=(nullptr_t nullpointer) 
{ 
    (void)nullpointer; 
    Value.m_hasValue = false; 
    Value.m_value = T(); 
    return *this; 
} 

he comprobado en gcc, sonido metálico y vs15 con lo siguiente:

#include <iostream> 
using namespace std; 

int main(int argc, char* argv[]) 
{ 
    (void)argc; 
    (void)argv; 

    Nullable<int> ni1; 
    Nullable<int> ni2 = nullptr; 
    Nullable<int> ni3 = 3; 
    Nullable<int> ni4 = 4; 
    ni4 = nullptr; 
    Nullable<int> ni5 = 5; 
    Nullable<int> ni6; 
    ni6 = ni3; 
    Nullable<int> ni7(ni3); 
    //Nullable<int> ni8 = NULL; // This is an error in gcc/clang but it's ok in VS12 

    cout << (ni1 == nullptr ? "True" : "False") << endl; // True 
    cout << (ni2 == nullptr ? "True" : "False") << endl; // True 
    cout << (ni2 == 3 ? "True" : "False") << endl; // False 
    cout << (ni2 == ni3 ? "True" : "False") << endl; // False 
    cout << (ni3 == 3 ? "True" : "False") << endl; // True 
    cout << (ni2 == ni4 ? "True" : "False") << endl; // True 
    cout << (ni3 == ni5 ? "True" : "False") << endl; // False 
    cout << (ni3 == ni6 ? "True" : "False") << endl; // True 
    cout << (ni3 == ni7 ? "True" : "False") << endl; // True 

    //cout << ni1 << endl; // Doesn't compile 
    //cout << ni3 << endl; // Doesn't compile 
    cout << ni3.Value << endl; // 3 
    //cout << ni1.Value << endl; // Throw exception 
    //cout << ni2.Value << endl; // Throw exception 
    //ni3.Value = 2; // Doesn't compile 
    cout << sizeof(ni1) << endl; // 8 on VS15 

    return 0; 
} 
+0

hay un error tipográfico en una de las sobrecargas de! =: "If (! Op.Value.true)" – AaronHS

+0

@AaronHS gracias. Se corrigió y también se agregó semántica más útil (métodos TryGet) – ceztko

2

Hay gran cantidad de implementación del tipo anulable para C++ y la mayoría son incompletos. En el mundo C++, tipos que aceptan nulos se llaman tipos opcionales. Esto fue propuesto para C++ 14 pero se pospuso. Sin embargo, el código para implementarlo compila y funciona en la mayoría de los compiladores de C++ 11.Puede simplemente dejar en el archivo de cabecera única aplicación de tipo opcional y empezar a utilizarlo: el uso

https://raw.githubusercontent.com/akrzemi1/Optional/master/optional.hpp

muestra:

#if (defined __cplusplus) && (__cplusplus >= 201700L) 
#include <optional> 
#else 
#include "optional.hpp" 
#endif 

#include <iostream> 

#if (defined __cplusplus) && (__cplusplus >= 201700L) 
using std::optional; 
#else 
using std::experimental::optional; 
#endif 

int main() 
{ 
    optional<int> o1,  // empty 
        o2 = 1, // init from rvalue 
        o3 = o2; // copy-constructor 

    if (!o1) { 
     cout << "o1 has no value"; 
    } 

    std::cout << *o2 << ' ' << *o3 << ' ' << *o4 << '\n'; 
} 

Más documentación: http://en.cppreference.com/w/cpp/experimental/optional

También vea mi otra respuesta: https://stackoverflow.com/a/37624595/207661

Cuestiones relacionadas