2010-04-01 103 views
11

如何在C++類中擁有屬性,就像在C#類中一樣。擁有C++類中的公共屬性

我不想有getter和setter方法。

+2

看看這裏的類似問題:http://stackoverflow.com/questions/2017623/forward-unbreakable-accessor-class-templates-c。我們利用C++特性甩掉了一些實現屬性的方法。 – 2010-04-01 11:30:07

+0

平臺特定的或平臺無關的C++?在Windows中,VC++編譯器具有支持此功能的非標準擴展。請參閱下面的答案。 – 2010-04-01 20:12:21

回答

11

您可以使用類似於Jon所建議的解決方案,但使用運算符重載保留普通的C++語義。我稍微修改Jon的代碼如下(解釋遵循的代碼):

#include <iostream> 

template<typename T> 
class Accessor { 
public: 
    explicit Accessor(const T& data) : value(data) {} 

    Accessor& operator=(const T& data) { value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { this->value = other.value; return *this; } 
    operator T() const { return value; } 
    operator T&() { return value; } 

private: 
    Accessor(const Accessor&); 


    T value; 

}; 

struct Point { 
    Point(int a = 0, int b = 0) : x(a), y(b) {} 
    Accessor<int> x; 
    Accessor<int> y; 
}; 

int main() { 
    Point p; 
    p.x = 10; 
    p.y = 20; 
    p.x++; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

我們超載operator=保留了通常的賦值語法,而不是一個函數調用的語法。我們使用演員操作符作爲「getter」。我們需要operator=的第二個版本來允許在main()中分配第二種類型。

現在,您可以添加到Accessor的構造函數指針或更好的函數 - 以任何方式調用getter/setters似乎是正確的。下面的示例假設setter函數返回布爾傳達協議設置新值,吸氣劑可以只修改它的出路:

#include <iostream> 
#include <functional> 
#include <cmath> 

template<typename T> 
class MySetter { 
public: 
    bool operator()(const T& data) 
    { 
     return (data <= 20 ? true : false); 
    } 
}; 

template<typename T> 
class MyGetter { 
public: 
    T operator()(const T& data) 
    { 
     return round(data, 2); 
    } 

private: 
    double cint(double x) { 
     double dummy; 
     if (modf(x,&dummy) >= 0.5) { 
      return (x >= 0 ? ceil(x) : floor(x)); 
     } else { 
      return (x < 0 ? ceil(x) : floor(x)); 
     } 
    } 

    double round(double r, int places) { 
     double off = pow(10.0L, places); 
     return cint(r*off)/off; 
    } 
}; 

template<typename T, typename G = MyGetter<T>, typename S = MySetter<T>> 
class Accessor { 
public: 
    explicit Accessor(const T& data, const G& g = G(), const S& s = S()) : value(data), getter(g), setter(s) {} 

    Accessor& operator=(const T& data) { if (setter(data)) value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { if (setter(other.value)) this->value = other.value; return *this; } 
    operator T() const { value = getter(value); return value;} 
    operator T&() { value = getter(value); return value; } 

private: 
    Accessor(const Accessor&); 

    T value; 

    G getter; 
    S setter; 

}; 

struct Point { 
    Point(double a = 0, double b = 0) : x(a), y(b) {} 
    Accessor<double> x; 
    Accessor<double> y; 
}; 

int main() { 
    Point p; 
    p.x = 10.712; 
    p.y = 20.3456; 
    p.x+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15.6426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 25.85426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 19.8425; 
    p.y+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

然而,隨着最後一行表明,它有一個bug。返回T &的演員操作員允許用戶繞過設置者,因爲它允許他們訪問私有值。解決這個錯誤的一種方法是實現你希望Accessor提供的所有操作符。例如,在下面的代碼我使用了+ =運營商,因爲我脫離的鑄造操作返回參考我不得不實施operator+=

#include <iostream> 
#include <functional> 
#include <cmath> 

template<typename T> 
class MySetter { 
public: 
    bool operator()(const T& data) const { 
     return (data <= 20 ? true : false); 
    } 
}; 

template<typename T> 
class MyGetter { 
public: 
    T operator() (const T& data) const { 
     return round(data, 2); 
    } 

private: 
    double cint(double x) const { 
     double dummy; 
     if (modf(x,&dummy) >= 0.5) { 
      return (x >= 0 ? ceil(x) : floor(x)); 
     } else { 
      return (x < 0 ? ceil(x) : floor(x)); 
     } 
    } 

    double round(double r, int places) const { 
     double off = pow(10.0L, places); 
     return cint(r*off)/off; 
    } 
}; 

template<typename T, typename G = MyGetter<T>, typename S = MySetter<T>> 
class Accessor { 
private: 
public: 
    explicit Accessor(const T& data, const G& g = G(), const S& s = S()) : value(data), getter(g), setter(s) {} 

    Accessor& operator=(const T& data) { if (setter(data)) value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { if (setter(other.value)) this->value = other.value; return *this; } 
    operator T() const { return getter(value);} 

    Accessor& operator+=(const T& data) { if (setter(value+data)) value += data; return *this; } 

private: 
    Accessor(const Accessor&); 

    T value; 

    G getter; 
    S setter; 

}; 

struct Point { 
    Point(double a = 0, double b = 0) : x(a), y(b) {} 
    Accessor<double> x; 
    Accessor<double> y; 
}; 

int main() { 
    Point p; 
    p.x = 10.712; 
    p.y = 20.3456; 
    p.x+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15.6426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 25.85426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 19.8425; 
    p.y+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

你必須實現所有你要的運營商使用。

+0

+ 1擊敗了我,但我不喜歡這種提供大量運算符重載的想法。另一種處理外部失效值的方法是提供一個不同的「被寫入」回調函數,當下一次讀取值時會調用它。 – 2010-04-02 00:08:25

+0

考慮到你只在模板類中定義了所有這些運算符,我不認爲這太可怕了。在我看來,STL或boost代碼與此相比(以及一般來說太:))是可怕的。 但也有其他選擇。必須考慮自己的情況並據此作出選擇。 :) – conio 2010-04-04 00:52:50

1

你不知道。 C++不支持像C#那樣的屬性。如果你想讓代碼在set/get上運行,它必須是一個方法。

+0

屬性真的可以在C#中運行方法。編譯器將它從你身上隱藏起來。 – 2010-04-01 19:46:31

7

對於這樣的行爲,我使用模板化的元訪問器。這是一個高度簡化的一個POD類型:

template<class T> 
struct accessor { 

    explicit accessor(const T& data) : value(data) {} 
    T operator()() const { return value; } 
    T& operator()() { return value; } 
    void operator()(const T& data) { value = data; } 

private: 

    accessor(const accessor&); 
    accessor& operator=(const accessor&); 
    T value; 

}; 

典型用法是這樣的:

struct point { 
    point(int a = 0, int b = 0) : x(a), y(b) {} 
    accessor<int> x; 
    accessor<int> y; 
}; 

point p; 
p.x(10); 
p.y(20); 
p.x()++; 
std::cout << p.x(); 

編譯器,如果你設置的東西,並有權利優化開啓通常內聯這些調用。與使用實際的getter和setter不同,它不再是性能瓶頸,無論發生什麼優化。將它擴展爲自動支持非POD或枚舉類型或允許在讀取或寫入數據時允許註冊回調是微不足道的。

編輯:如果您不想使用括號,則可以始終定義operator=()和隱式轉換運算符。這裏有一個版本可以做到這一點,同時也添加了基本的「東西發生」回調支持:

更多編輯:好的,完全錯過了某人已經做了我的代碼的修訂版本。嘆。

+0

當你可以定義operator =時,爲什麼使用笨重的函子風格? – 2010-04-01 23:24:16

+0

@Ben:爲完整性添加差異。它忽略了C#中的那些垃圾。 – 2010-04-01 23:59:45

1

屬性是不是在C++的支持,但你可以實現它們:
1)通過使用模板
2)通過使語言的擴展和編寫自定義代碼預處理器

兩種方法都不會是一帆風順的,但這是可以完成的。

+0

C++支持重載operator =,所以不需要擴展。 – 2010-04-01 23:21:33

+0

@ Ben Voigt這取決於你想要實現什麼,以及你想要的屬性數量。使用大量的代碼,引入一個或兩個關鍵字並編寫代碼預處理器將是更好的主意 - 它將使代碼更具可讀性。 – SigTerm 2010-04-02 15:22:20

3

這是我一段時間後做的一個PoC實現,除了需要在構造函數中設置一些東西以使其良好且流暢地工作以外,其效果很好。

http://www.codef00.com/code/Property.h

這裏的用法示例:

#include <iostream> 
#include "Property.h" 


class TestClass { 
public: 
    // make sure to initialize the properties with pointers to the object 
    // which owns the property 
    TestClass() : m_Prop1(0), m_Prop3(0.5), prop1(this), prop2(this), prop3(this) { 
    } 

private: 
    int getProp1() const { 
     return m_Prop1; 
    } 

    void setProp1(int value) { 
     m_Prop1 = value; 
    } 

    int getProp2() const { 
     return 1234; 
    } 

    void setProp3(double value) { 
     m_Prop3 = value; 
    } 

    int m_Prop1; 
    double m_Prop3; 

public: 
    PropertyRW<int, TestClass, &TestClass::getProp1, &TestClass::setProp1> prop1; 
    PropertyRO<int, TestClass, &TestClass::getProp2> prop2; 
    PropertyWO<double, TestClass, &TestClass::setProp3> prop3; 
}; 

和這個類的一些使用...

int main() { 
    unsigned int a; 
    TestClass t; 
    t.prop1 = 10; 
    a = t.prop1; 
    t.prop3 = 5; 
    a = t.prop2; 
    std::cout << a << std::endl; 
    return 0; 
} 

有兩個煩惱這種方法:

  1. 你需要克屬性a 指向其擁有的類。
  2. 的語法來聲明一個屬性是 有點冗長,但我敢打賭,我可以清理 說了一下一些宏
+0

有點尷尬,是的,但使用'this'註冊訪問器的想法很好,因爲它很容易讓屬性根據實例和類型信息玩弄技巧。 – 2010-04-02 00:20:10

1

您可以提供具有類似名稱的數據成員get和set方法:

class Example 
{ 
    private: 
    unsigned int x_; 
    double d_; 
    std::string s_s; 
    public: 
    unsigned int x(void) const 
    { return x_;} 

    void x(unsigned int new_value) 
    { x_ = new_value;} 

    double d(void) const 
    { return d_;} 
    void d(double new_value) 
    { d_ = new_value;} 

    const std::string& s(void) const 
    { return s_;} 
    void s(const std::string& new_value) 
    { s_ = new_value;} 
}; 

雖然這種接近,因爲它需要使用「()」的每個成員,它不符合性能,微軟的語言提供具體的功能。

最接近的屬性匹配是聲明數據成員爲公共。

+0

錯誤的是,通過重載operator =,你可以得到確切的語法(對於用戶)和爲你提供屬性的任何語言的能力。 – 2010-04-01 23:23:02

3

如果您不關心您的C++代碼不能用Microsoft Visual C++編譯器以外的任何其他編譯器編譯,那麼您可以使用某些編譯器的非標準擴展。

例如,下面的代碼將創建一個名爲MyProperty的C#類屬性。

struct MyType 
{ 
    // This function pair may be private (for clean encapsulation) 
    int get_number() const { return m_number; } 
    void set_number(int number) { m_number = number; } 

    __declspec(property(get=get_number, put=set_number)) int MyProperty; 
private: 
    int m_number: 
} 

int main() 
{ 
    MyType m; 
    m.MyProperty = 100; 
    return m.MyProperty; 
} 

有關此特定於Microsoft的語言擴展的更多信息,請訪問here