2010-03-29 38 views
24

Tôi đang tạo một lớp truy cập cơ sở dữ liệu trong native C++, và tôi đang tìm cách để hỗ trợ các giá trị NULL. Dưới đây là những gì tôi có cho đến nay:Nullable giá trị trong 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; 
}; 

Đây là cách tôi sẽ phải xác định các chức năng:

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

Và tôi gọi nó là như thế này:

sp.StoredProc(1, 2); 

hoặc

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

Tôi đã tự hỏi liệu có tốt hơn không cách này hơn. Đặc biệt tôi không thích đối tượng giống như singleton của CNullValue với các số liệu thống kê. Tôi muốn chỉ làm

sp.StoredProc(3, CNullValue); 

hoặc tương tự. Người khác giải quyết vấn đề này như thế nào?

Trả lời

27

Boost.Optional có thể làm những gì bạn cần.

boost::none thay thế địa chỉ CNullValue::Null() của bạn. Vì đó là một giá trị chứ không phải là cuộc gọi hàm thành viên, bạn có thể làm using boost::none; nếu bạn muốn, ngắn gọn. Nó có một chuyển đổi để bool thay vì IsNull, và operator* thay vì GetValue, vì vậy bạn sẽ làm:

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

Nhưng những gì bạn đã đưa ra chủ yếu là thiết kế tương tự, tôi nghĩ.

+0

Đặc biệt là xem xét nó tương đương với giá trị nhúng hiệu quả khôn ngoan vì chúng không sử dụng phân bổ đống. –

+0

Cảm ơn bạn, hãy xem thư viện này ngay bây giờ ... – DanDan

+0

Chỉ cần thử nghiệm nó. Nó là hoàn hảo. – DanDan

3

Thay thế IsNull bằng HasValue và bạn đã có loại .NET Nullable.

Tất nhiên .. đây là C++. Tại sao không chỉ sử dụng một con trỏ đến một loại "nguyên thủy"?

+8

Con trỏ có ngữ nghĩa sao chép khác nhau. Nếu bạn sao chép một con trỏ không null, thì bản sao đề cập đến cùng một đối tượng. Nếu bạn sao chép CNullableT này, bản sao có thể hiện riêng của nó về giá trị. Trong một số tình huống bạn muốn một và trong một số khác, nhưng đó là một vấn đề độc lập từ việc bạn muốn phạm vi của các giá trị là "bất kỳ T, hoặc không có". Vì vậy, sử dụng một con trỏ cho một giá trị tùy chọn mang lại một số hành lý. –

+1

Tôi chỉ cố gắng làm cho giao diện sạch hơn trong phần lớn các trường hợp, vì vậy tôi có thể làm StoredProcedcure (1, 2) thay vì int p = 2; StoredProcedcure (1, &p); Vâng, loại .NET Nullable được sử dụng làm nguồn cảm hứng :) – DanDan

+0

@Steve Jessop: điểm tuyệt vời mà tôi đã không xem xét. – Randolpho

12

EDIT: Cải thiện ngoại lệ ném trên giá trị "null". Thêm sửa lỗi

Nếu tăng không phải là tùy chọn, trong C++ 11, bạn cũng có thể tận dụng lợi thế của nullptr và kiểu nhập nullptr_t để tạo một Nullable<T> với ngữ nghĩa tương tự như .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; 
} 

Tôi đã thử nghiệm nó trong gcc, kêu vang và VS15 như sau:

#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ó lỗi đánh máy trong một trong các lỗi! = Overloads: "if (! Op.Value.true)" – AaronHS

+0

@AaronHS cảm ơn bạn. Đã sửa lỗi và cũng bổ sung thêm ngữ nghĩa hữu ích (phương thức TryGet) – ceztko

2

Có rất nhiều loại thực Nullable cho C++ và nhất là không đầy đủ. Trong thế giới C++, loại vô hiệu được gọi là các loại tùy chọn. Điều này đã được đề xuất cho C++ 14 nhưng đã bị trì hoãn. Tuy nhiên mã để thực hiện nó biên dịch và hoạt động trên hầu hết các trình biên dịch C++ 11.Bạn chỉ có thể thả trong file header đơn thực hiện loại tùy chọn và bắt đầu sử dụng nó: sử dụng

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

mẫu:

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

Nhiều tài liệu: http://en.cppreference.com/w/cpp/experimental/optional

Cũng xem câu trả lời khác của tôi: https://stackoverflow.com/a/37624595/207661

Các vấn đề liên quan