2010-04-01 34 views
11

Làm cách nào để có các thuộc tính trong lớp C++, như bạn có trong lớp C#.Có thuộc tính công khai trong lớp C++

Tôi không muốn có phương thức getter và setter.

+2

Hãy xem một câu hỏi tương tự tại đây: http://stackoverflow.com/questions/2017623/forward-unbreakable-accessor-class-templates-c. Chúng tôi đã loại bỏ một số cách để triển khai các thuộc tính bằng cách sử dụng các tính năng của C++. –

+0

C++ dành riêng cho nền tảng hoặc nền tảng độc lập? Trong Windows, trình soạn thảo VC++ có các phần mở rộng không chuẩn hỗ trợ điều này. Xem câu trả lời của tôi dưới đây. –

Trả lời

11

Bạn có thể sử dụng một giải pháp tương tự như Jon đã đề xuất, nhưng vẫn giữ lại ngữ nghĩa C++ thông thường bằng cách sử dụng toán tử quá tải. Tôi đã hơi đổi mã Jon như sau (giải thích theo mã):

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

Chúng tôi quá tải operator= để giữ lại cú pháp gán thông thường thay vì một cú pháp hàm gọi như thế nào. Chúng tôi sử dụng toán tử cast làm "getter". Chúng tôi cần phiên bản thứ hai của số operator= để cho phép gán loại thứ hai trong main().

Bây giờ bạn có thể thêm vào các hàm con trỏ hàm của trình xây dựng của Accessor hoặc tốt hơn - hàm functors - để gọi là getters/setters theo bất kỳ cách nào phù hợp với bạn. Ví dụ sau đây giả định setter chức năng trở lại bool để truyền đạt thỏa thuận để thiết lập các giá trị mới, và các getter chỉ có thể sửa đổi nó vào đó là lối thoát:

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

Tuy nhiên, như dòng cuối cùng chứng tỏ nó có một lỗi. Toán tử cast trả về T & cho phép người dùng bỏ qua setter, vì nó cho phép họ truy cập vào giá trị riêng. Một cách để giải quyết lỗi này là thực hiện tất cả các toán tử bạn muốn Accessor của bạn cung cấp.Ví dụ, trong đoạn mã sau tôi đã sử dụng + = điều hành, và kể từ khi tôi loại bỏ các tài liệu tham khảo điều hành đúc trở về tôi đã phải thực hiện một 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; 
} 

Bạn sẽ phải thực hiện tất cả các nhà khai thác bạn đang đi để sử dụng.

+0

+1 để đánh tôi với cú đấm, nhưng tôi không thích ý tưởng cung cấp hàng tấn quá tải của nhà điều hành như thế. Một cách khác để chăm sóc các giá trị có thể bên ngoài không hợp lệ là cung cấp một cuộc gọi lại "đã được viết" khác được gọi khi giá trị được đọc tiếp theo. –

+0

Xem xét rằng bạn chỉ xác định tất cả các toán tử đó một lần trong lớp mẫu, tôi không nghĩ rằng điều đó thật khủng khiếp. STL hoặc mã tăng là khủng khiếp so sánh với điều này (và nói chung quá :)) theo ý kiến ​​của tôi. Nhưng cũng có các tùy chọn khác. Người ta phải xem xét hoàn cảnh của một người và làm cho một sự lựa chọn của phù hợp. :) – conio

1

Bạn không. C++ không hỗ trợ các thuộc tính như C#. Nếu bạn muốn mã chạy trên set/get, nó sẽ phải là một phương thức.

+0

Các thuộc tính thực sự chạy các phương thức trong C#. Trình biên dịch giấu nó khỏi bạn. –

7

Đối với hành vi đó là loại như thế này, tôi sử dụng một meta-accessor templated. Dưới đây là một đơn giản hóa cao với nhiều loại 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; 

}; 

sử dụng điển hình là như thế này:

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(); 

Trình biên dịch thường inlines các cuộc gọi nếu bạn thiết lập những điều đúng và có tối ưu hóa bật. Nó không phải là một nút cổ chai hiệu suất hơn là sử dụng getters và setters thực tế, không có vấn đề gì tối ưu hóa xảy ra. Nó là tầm thường để mở rộng này để tự động hỗ trợ không POD hoặc các loại liệt kê, hoặc để cho phép callbacks được đăng ký cho bất cứ khi nào dữ liệu được đọc hoặc viết.

Chỉnh sửa: Nếu bạn không muốn sử dụng dấu ngoặc đơn, bạn luôn có thể xác định operator=() và toán tử ẩn ngầm. Dưới đây là phiên bản thực hiện điều đó, đồng thời cũng thêm hỗ trợ gọi lại "nội dung đã xảy ra" cơ bản:

Chỉnh sửa thêm: Được rồi, hoàn toàn bỏ lỡ ai đó đã thực hiện phiên bản sửa đổi mã của tôi. Thở dài.

+0

Tại sao bạn sử dụng phong cách functor clunky khi bạn có thể đã định nghĩa toán tử =? –

+0

@Ben: Đã thêm sự khác biệt cho tính đầy đủ. Nó trượt tâm trí của tôi như thế nào crap trông trong C#. –

1

Thuộc tính không được hỗ trợ trong C++, nhưng bạn có thể thực hiện chúng:
1) Bằng cách sử dụng các mẫu
2) Bằng cách làm cho phần mở rộng ngôn ngữ và viết mã tùy chỉnh Preprocessor

Hoặc cách tiếp cận sẽ không dễ dàng, Nhưng nó có thể được thực hiện.

+0

C++ hỗ trợ quá tải toán tử =, do đó không cần mở rộng. –

+0

@Ben Voigt Điều này phụ thuộc vào chính xác những gì bạn muốn triển khai và số lượng thuộc tính bạn muốn. Với số lượng lớn mã, việc giới thiệu một từ khóa hoặc hai và viết mã tiền xử lý sẽ là ý tưởng tốt hơn - nó sẽ làm cho mã dễ đọc hơn. – SigTerm

3

Đây là một triển khai PoC tôi đã làm một lúc trở lại, hoạt động độc đáo ngoại trừ việc bạn cần phải thiết lập một cái gì đó trong constructor để nó hoạt động tốt và mượt mà.

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

Dưới đây là cách sử dụng ví dụ:

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

và một số sử dụng của lớp này ...

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

Có hai phiền toái với cách tiếp cận này:

  1. Bạn cần phải g ive thuộc tính một con trỏ đến lớp sở hữu của nó.
  2. Cú pháp để khai báo một tài sản là một tiết chút, nhưng tôi đặt cược tôi có thể làm sạch đó lên một chút với một số macro
+0

Một chút lúng túng, vâng, nhưng ý tưởng đăng ký accessor với 'this' là một điều tốt, vì nó dễ dàng cho phép các thuộc tính chơi thủ thuật với những người có thể truy cập chúng dựa trên thông tin cá thể và loại. –

1

Bạn có thể cung cấp được và thiết lập các phương pháp có tên tương tự cho các thành viên dữ liệu :

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

Mặc dù đây đến gần, vì nó đòi hỏi phải sử dụng '()' cho từng thành viên, nó không đáp ứng các chức năng chính xác của tính rằng Microsoft Ngôn ngữ cung cấp.

Đối sánh gần nhất cho thuộc tính là khai báo thành viên dữ liệu là công khai.

+0

Sai, do quá tải toán tử = bạn có thể nhận cú pháp chính xác (đối với người dùng) và khả năng của bất kỳ ngôn ngữ nào cung cấp các thuộc tính cho bạn. –

3

Nếu bạn không quan tâm rằng mã C++ của bạn sẽ không biên dịch với bất kỳ thứ gì khác ngoài trình biên dịch Microsoft Visual C++, thì bạn có thể sử dụng một số phần mở rộng không chuẩn của trình biên dịch. Ví dụ:

Ví dụ: mã sau sẽ tạo thuộc tính C# giống như được gọi là MyProperty.

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

Thông tin thêm về tiện ích mở rộng ngôn ngữ cụ thể của Microsoft này có sẵn here.

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