Nullable values in C++

后端 未结 4 992
一整个雨季
一整个雨季 2020-12-01 17:48

I\'m creating a database access layer in native C++, and I\'m looking at ways to support NULL values. Here is what I have so far:

class CNullValue
{
public:         


        
相关标签:
4条回答
  • 2020-12-01 18:14

    EDIT: Improved with throw exception on "null" Value. More fixes

    If boost is not an option, in c++11 you can also take advantage of nullptr and the nullptr_t typedef to create a Nullable<T> with pretty much same semantics as .NET one.

    #pragma once
    
    #include <cstddef>
    #include <stdexcept>
    
    template <typename T>
    class Nullable final
    {
    public:
        Nullable();
        Nullable(const T &value);
        Nullable(std::nullptr_t nullpointer);
        const Nullable<T> & operator=(const Nullable<T> &value);
        const Nullable<T> & operator=(const T &value);
        const Nullable<T> & operator=(std::nullptr_t nullpointer);
        bool HasValue() const;
        const T & GetValueOrDefault() const;
        const T & GetValueOrDefault(const T &def) const;
        bool TryGetValue(T &value) const;
        T * operator->();
        const T * operator->() const;
        T & operator*();
        const T & operator*() 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, std::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==(std::nullptr_t nullpointer, const Nullable<T2> &op);
    
            template <typename T2>
            friend bool operator!=(const Nullable<T2> &op, std::nullptr_t nullpointer);
    
            template <typename T2>
            friend bool operator!=(std::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::runtime_error("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 &def) const
    {
        if (Value.m_hasValue)
            return Value.m_value;
        else
            return def;
    }
    
    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(std::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, std::nullptr_t nullpointer)
    {
        (void)nullpointer;
        return !op.Value.m_hasValue;
    }
    
    template <typename T2>
    bool operator==(std::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, std::nullptr_t nullpointer)
    {
        (void)nullpointer;
        return op.Value.m_hasValue;
    }
    
    template <typename T2>
    bool operator!=(std::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=(std::nullptr_t nullpointer)
    {
        (void)nullpointer;
        Value.m_hasValue = false;
        Value.m_value = T();
        return *this;
    }
    
    template <typename T>
    T * Nullable<T>::operator->()
    {
        return &Value.m_value;
    }
    
    template <typename T>
    const T * Nullable<T>::operator->() const
    {
        return &Value.m_value;
    }
    
    template <typename T>
    T & Nullable<T>::operator*()
    {
        return Value.m_value;
    }
    
    template <typename T>
    const T & Nullable<T>::operator*() const
    {
        return Value.m_value;
    }
    

    I tested it in gcc, clang and VS15 with the following:

    #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 讨论(0)
  • 2020-12-01 18:19

    There are lot of Nullable type implementation for C++ and most are incomplete. In C++ world, nullable types are called optional types. This was proposed for C++14 but got postponed. However the code to implement it compiles and works on most C++11 compilers. You can just drop in the single header file implementing optional type and start using it:

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

    Sample usage:

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

    More documentation: http://en.cppreference.com/w/cpp/experimental/optional

    Also see my other answer: https://stackoverflow.com/a/37624595/207661

    0 讨论(0)
  • 2020-12-01 18:21

    Replace IsNull with HasValue and you've got the .NET Nullable type.

    Of course.. this is C++. Why not just use a pointer to a "primitive" type?

    0 讨论(0)
  • 2020-12-01 18:30

    Boost.Optional probably does what you need.

    boost::none takes the place of your CNullValue::Null(). Since it's a value rather than a member function call, you can do using boost::none; if you like, for brevity. It has a conversion to bool instead of IsNull, and operator* instead of GetValue, so you'd do:

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

    But what you've come up with is essentially the same design, I think.

    0 讨论(0)
提交回复
热议问题