2010-03-29 17 views
24

Sto creando un livello di accesso al database in C++ nativo e sto cercando modi per supportare valori NULL. Ecco quello che ho finora:Valori Nullable in 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; 
}; 

questo è come mi dovrò definire funzioni:

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

E mi chiamare in questo modo:

sp.StoredProc(1, 2); 

o

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

Mi stavo chiedendo se ci fosse un migliore modo di questo. In particolare, non mi piace l'oggetto simile a un singleton di CNullValue con la statica. Preferirei fare proprio

sp.StoredProc(3, CNullValue); 

o qualcosa di simile. Come fanno gli altri a risolvere questo problema?

risposta

27

Boost.Optional probabilmente fa quello che ti serve.

boost::none prende il posto del tuo CNullValue::Null(). Dal momento che si tratta di un valore piuttosto che di una chiamata di funzione membro, è possibile eseguire using boost::none; se lo si desidera, per brevità. Ha una conversione a bool invece di IsNull, e operator* invece di GetValue, in modo da farebbe:

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

Ma quello che è venuta in mente è essenzialmente lo stesso disegno, penso.

+0

Soprattutto se si considera che è equivalente al valore intrinseco delle prestazioni poiché non utilizzano l'allocazione dell'heap. –

+0

Grazie, esaminando questa libreria ora ... – DanDan

+0

Appena testato. È perfetto. – DanDan

3

Sostituire IsNull con HasValue e il tipo .NET Nullable.

Ovviamente .. questo è C++. Perché non usare un puntatore solo su un tipo "primitivo"?

+8

I puntatori hanno una semantica della copia diversa. Se copi un puntatore non nullo, la copia fa riferimento allo stesso oggetto. Se copi questo CNullableT, la copia ha la sua istanza del valore. In alcune situazioni ne volete uno e in alcuni l'altro, ma questo è un problema indipendente dal fatto che volete che l'intervallo di valori sia "qualsiasi T o nessuno". Quindi usare un puntatore per un valore opzionale porta qualche bagaglio. –

+1

Cercavo solo per rendere più pulita interfaccia per la maggior parte dei casi, quindi posso fare StoredProcedcure (1, 2) anziché int p = 2; StoredProcedcure (1, &p); Sì, il tipo .NET Nullable è stato utilizzato come fonte di ispirazione :) – DanDan

+0

@Steve Jessop: punto eccellente che non avevo considerato. – Randolpho

12

MODIFICA: Migliorato con l'eccezione di lancio sul valore "null". Ulteriori correzioni

Se spinta non è un'opzione, in C++ 11 si può inoltre usufruire di nullptr e il typedef nullptr_t per creare un Nullable<T> con più o meno stessa semantica .NET uno.

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

ho provato a gcc, clang e VS15 con il seguente:

#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

c'è un errore di battitura in uno dei! = Overload: "if (! Op.Value.true)" – AaronHS

+0

@AaronHS grazie. Risolto e anche aggiunto semantica più utile (metodi TryGet) – ceztko

2

Ci sono molte implementazione tipo Nullable per C++ e la maggior parte sono incompleti. Nel mondo C++, i tipi nulli sono chiamati tipi opzionali. Questo è stato proposto per C++ 14 ma è stato posticipato. Tuttavia il codice per implementarlo compila e funziona sulla maggior parte dei compilatori C++ 11.Si può solo cadere nel singolo file di intestazione attuazione tipo facoltativo e iniziare ad usarlo: utilizzo

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

Esempio:

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

Più documentazione: http://en.cppreference.com/w/cpp/experimental/optional

vedere anche la mia altra risposta: https://stackoverflow.com/a/37624595/207661