簡體   English   中英

C++ 中的可空值

[英]Nullable values in C++

我正在用本機 C++ 創建一個數據庫訪問層,並且正在尋找支持 NULL 值的方法。 這是我到目前為止所擁有的:

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

這就是我必須定義函數的方式:

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

我這樣稱呼它:

sp.StoredProc(1, 2);

或者

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

我只是想知道是否有比這更好的方法。 特別是我不喜歡帶有靜態的 CNullValue 的單例對象。 我寧願做

sp.StoredProc(3, CNullValue);

或類似的東西。 其他人如何解決這個問題?

Boost.Optional可能滿足您的需求。

boost::none取代了你的CNullValue::Null() 由於它是一個值而不是成員函數調用,因此您可以using boost::none; 如果你願意,為了簡潔。 它有一個轉換為bool而不是IsNulloperator*而不是GetValue ,所以你會這樣做:

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

但我認為你想出的設計本質上是相同的。

EDIT1 :改進了“null”值拋出異常。 更多修復

EDIT2 : 可以在此處找到支持可為空引用的替代版本。

如果Boost.Optionalstd::optional不是一個選項,在 c++11 中,您還可以利用nullptrnullptr_t typedef 創建一個Nullable<T> ,其語義與 .NET 幾乎相同。

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

我在 gcc、clang 和 VS15 中對其進行了以下測試:

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

C++ 有很多 Nullable 類型的實現,而且大多數都不完整。 在 C++ 世界中,可為空的類型稱為可選類型 這是為 C++14 提出的,但被推遲了。 然而,實現它的代碼可以在大多數 C++11 編譯器上編譯和工作。 您可以只放入實現可選類型的單個頭文件並開始使用它:

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

示例用法:

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

更多文檔: http ://en.cppreference.com/w/cpp/experimental/optional

另請參閱我的其他答案: https ://stackoverflow.com/a/37624595/207661

IsNull替換為HasValue ,您就獲得了 .NET Nullable類型。

當然..這是C++。 為什么不直接使用指向“原始”類型的指針?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM