C ++ 11 có thuộc tính kiểu C # không?


93

Trong C #, có một đường cú pháp tuyệt vời cho các trường với getter và setter. Hơn nữa, tôi thích các thuộc tính được triển khai tự động cho phép tôi viết

public Foo foo { get; private set; }

Trong C ++ tôi phải viết

private:
    Foo foo;
public:
    Foo getFoo() { return foo; }

Có một số khái niệm như vậy trong C ++ 11 cho phép tôi có một số cú pháp về điều này không?


62
Nó có thể được thực hiện với một vài macro. bỏ chạy trong sự xấu hổ
Mankarse

7
@Eloff: Chỉ công khai mọi thứ LUÔN LUÔN là một ý kiến ​​tồi.
Kaiserludi

8
Không có khái niệm đó! Và bạn không cần nó quá: seanmiddleditch.com/why-c-does-not-need-c-like-properties
CinCout

2
a) câu hỏi này khá cũ b) Tôi đang yêu cầu đường cú pháp, điều đó sẽ cho phép tôi loại bỏ dấu ngoặc đơn c) mặc dù bài báo trình bày các đối số hợp lệ chống lại các thuộc tính thích ứng, cho dù thuộc tính C ++ 'có hay không cần' là rất chủ quan. C ++ tương đương với Touring-machine ngay cả khi không có chúng, nhưng điều đó không có nghĩa là việc có đường cú pháp như vậy sẽ làm cho C ++ hiệu quả hơn.
Radim Vansa

3
Chắc chắn không phải.

Câu trả lời:


87

Trong C ++, bạn có thể viết các tính năng của riêng mình. Đây là một ví dụ triển khai các thuộc tính bằng cách sử dụng các lớp không tên. Bài viết trên Wikipedia

struct Foo
{
    class {
        int value;
        public:
            int & operator = (const int &i) { return value = i; }
            operator int () const { return value; }
    } alpha;

    class {
        float value;
        public:
            float & operator = (const float &f) { return value = f; }
            operator float () const { return value; }
    } bravo;
};

Bạn có thể viết getters & setters của riêng mình tại chỗ và nếu bạn muốn quyền truy cập thành viên lớp chủ sở hữu, bạn có thể mở rộng mã ví dụ này.


1
Bất kỳ ý tưởng nào về cách sửa đổi mã này để vẫn có biến thành viên riêng tư mà Foo có thể truy cập nội bộ, trong khi API công khai chỉ hiển thị thuộc tính? Tất nhiên, tôi có thể chỉ cho Foo làm bạn của alpha / beta, nhưng sau đó tôi vẫn phải viết alpha.value, để truy cập giá trị, nhưng tôi muốn truy cập trực tiếp vào biến thành viên từ bên trong Foo sẽ cảm thấy thích hơn truy cập một thành viên của chính Foo và không phải là thành viên của một lớp tài sản lồng nhau đặc biệt.
Kaiserludi

1
@Kaiserludi Có: trong trường hợp này, hãy đặt alpha và bravo ở chế độ riêng tư. Trong Foo, bạn có thể đọc / ghi với những "thuộc tính" ở trên, nhưng bên ngoài Foo, điều này sẽ không còn khả thi nữa. Để phá vỡ điều đó, hãy tạo một tham chiếu const là công khai. Nó có thể truy cập từ bên ngoài, nhưng chỉ để đọc vì nó là một tham chiếu liên tục. Lưu ý duy nhất là bạn sẽ cần một tên khác cho tham chiếu const công cộng. Cá nhân tôi muốn sử dụng _alphacho biến private và alphađể tham khảo.
Kapichu

1
@Kapichu: không phải là một giải pháp, vì 2 lý do. 1) Trong C # thuộc tính getters / setters thường được sử dụng để nhúng kiểm tra độ an toàn bắt buộc đối với người dùng công khai của lớp trong khi cho phép các hàm thành viên truy cập trực tiếp giá trị. 2) Tham chiếu const không miễn phí: tùy thuộc vào trình biên dịch / nền tảng mà chúng sẽ phóng to sizeof(Foo).
ceztko

@psx: vì những hạn chế của cách tiếp cận này, tôi sẽ tránh nó và đợi bổ sung thích hợp vào tiêu chuẩn, nếu nó xuất hiện.
ceztko

@Kapichu: Nhưng alpha và bravo trong mã ví dụ là các thuộc tính. Tôi muốn truy cập trực tiếp vào chính biến từ bên trong quá trình triển khai Foo mà không cần sử dụng thuộc tính, trong khi tôi chỉ muốn để lộ quyền truy cập thông qua một thuộc tính trong API.
Kaiserludi,

53

C ++ không tích hợp sẵn tính năng này, bạn có thể xác định một mẫu để bắt chước chức năng thuộc tính:

template <typename T>
class Property {
public:
    virtual ~Property() {}  //C++11: use override and =default;
    virtual T& operator= (const T& f) { return value = f; }
    virtual const T& operator() () const { return value; }
    virtual explicit operator const T& () const { return value; }
    virtual T* operator->() { return &value; }
protected:
    T value;
};

Để xác định một thuộc tính :

Property<float> x;

Để triển khai getter / setter tùy chỉnh, chỉ cần kế thừa:

class : public Property<float> {
    virtual float & operator = (const float &f) { /*custom code*/ return value = f; }
    virtual operator float const & () const { /*custom code*/ return value; }
} y;

Để xác định thuộc tính chỉ đọc :

template <typename T>
class ReadOnlyProperty {
public:
    virtual ~ReadOnlyProperty() {}
    virtual operator T const & () const { return value; }
protected:
    T value;
};

Và để sử dụng nó trong lớpOwner :

class Owner {
public:
    class : public ReadOnlyProperty<float> { friend class Owner; } x;
    Owner() { x.value = 8; }
};

Bạn có thể xác định một số điều trên trong macro để làm cho nó ngắn gọn hơn.


Tôi tò mò nếu điều này biên dịch xuống một tính năng bằng không, tôi không biết liệu việc gói mỗi thành viên dữ liệu trong một cá thể lớp có dẫn đến cùng một kiểu đóng gói cấu trúc hay không.
Dai

1
Logic "custom getter / setter" có thể được làm rõ hơn về mặt cú pháp bằng cách sử dụng các hàm lambda, tiếc là bạn không thể xác định lambda bên ngoài ngữ cảnh thực thi trong C ++ (chưa!), Vì vậy nếu không sử dụng macro bộ xử lý trước, bạn sẽ chỉ có mã điên cuồng như getters / setters ngu ngốc, thật không may.
Đại

2
"Lớp: ..." trong ví dụ cuối cùng là thú vị và bị thiếu trong các ví dụ khác. Nó tạo ra một khai báo kết bạn cần thiết - mà không cần giới thiệu tên lớp mới.
Hans Olsson

Một sự khác biệt lớn giữa câu trả lời này và câu trả lời 2010-ngày 19 tháng 11 là điều này cho phép ghi đè getter hoặc setter trên cơ sở từng trường hợp. Bằng cách đó, người ta có thể kiểm tra đầu vào để nằm trong phạm vi, hoặc gửi thông báo thay đổi để thay đổi người nghe sự kiện hoặc nơi để treo điểm ngắt.
Eljay

28

Không có gì trong ngôn ngữ C ++ sẽ hoạt động trên tất cả các nền tảng và trình biên dịch.

Nhưng nếu bạn sẵn sàng phá vỡ khả năng tương thích đa nền tảng và cam kết với một trình biên dịch cụ thể, bạn có thể sử dụng cú pháp như vậy, ví dụ: trong Microsoft Visual C ++, bạn có thể làm

// declspec_property.cpp  
struct S {  
   int i;  
   void putprop(int j) {   
      i = j;  
   }  

   int getprop() {  
      return i;  
   }  

   __declspec(property(get = getprop, put = putprop)) int the_prop;  
};  

int main() {  
   S s;  
   s.the_prop = 5;  
   return s.the_prop;  
}


18

Bạn có thể mô phỏng getter và setter ở một mức độ nào đó bằng cách có một thành viên của loại chuyên dụng và ghi đè operator(type)operator=cho nó. Liệu đó có phải là một ý tưởng hay hay không lại là một câu hỏi khác và tôi sẽ đi đến +1câu trả lời của Kerrek SB để bày tỏ ý kiến ​​của mình về vấn đề đó :)


Bạn có thể mô phỏng gọi một phương thức khi gán cho hoặc đọc từ theo kiểu như vậy nhưng bạn không thể phân biệt ai gọi thao tác gán (để cấm nó nếu đó không phải là chủ sở hữu trường) - điều tôi đang cố gắng thực hiện bằng cách chỉ định quyền truy cập khác cấp cho getter và setter.
Radim Vansa

@Flavius: Chỉ cần thêm một friendchủ sở hữu trường.
kennytm

17

Có thể hãy xem lớp thuộc tính mà tôi đã tập hợp trong những giờ qua: /codereview/7786/c11-feedback-on-my-approach-to-c-like-class-properties

Nó cho phép bạn có các thuộc tính hoạt động như sau:

CTestClass myClass = CTestClass();

myClass.AspectRatio = 1.4;
myClass.Left = 20;
myClass.Right = 80;
myClass.AspectRatio = myClass.AspectRatio * (myClass.Right - myClass.Left);

Tốt, tuy nhiên, mặc dù điều này cho phép người truy cập do người dùng xác định nhưng không có tính năng getter công khai / setter riêng tư mà tôi đang tìm kiếm.
Radim Vansa

17

Với C ++ 11, bạn có thể xác định mẫu lớp Thuộc tính và sử dụng nó như sau:

class Test{
public:
  Property<int, Test> Number{this,&Test::setNumber,&Test::getNumber};

private:
  int itsNumber;

  void setNumber(int theNumber)
    { itsNumber = theNumber; }

  int getNumber() const
    { return itsNumber; }
};

Và đây là mẫu lớp Thuộc tính.

template<typename T, typename C>
class Property{
public:
  using SetterType = void (C::*)(T);
  using GetterType = T (C::*)() const;

  Property(C* theObject, SetterType theSetter, GetterType theGetter)
   :itsObject(theObject),
    itsSetter(theSetter),
    itsGetter(theGetter)
    { }

  operator T() const
    { return (itsObject->*itsGetter)(); }

  C& operator = (T theValue) {
    (itsObject->*itsSetter)(theValue);
    return *itsObject;
  }

private:
  C* const itsObject;
  SetterType const itsSetter;
  GetterType const itsGetter;
};

2
những gì hiện C::*có nghĩa là? Tôi chưa bao giờ nhìn thấy bất cứ điều gì giống như nó trước?
Rika

1
Nó là một con trỏ đến một hàm thành viên không tĩnh trong lớp C. Điều này tương tự như một con trỏ hàm thuần túy nhưng để gọi hàm thành viên, bạn cần cung cấp một đối tượng mà hàm được gọi. Điều này đạt được với dòng itsObject->*itsSetter(theValue)trong ví dụ trên. Xem tại đây để có mô tả chi tiết hơn về tính năng này.
Christoph Böhme

@Niceman, có ví dụ về cách sử dụng không? Có vẻ như rất tốn kém để có một thành viên. Nó cũng không đặc biệt hữu ích như một thành viên tĩnh.
Grim Fandango

16

Như nhiều người khác đã nói, không có hỗ trợ tích hợp trong ngôn ngữ. Tuy nhiên, nếu bạn đang nhắm mục tiêu trình biên dịch C ++ của Microsoft, bạn có thể tận dụng tiện ích mở rộng dành riêng cho Microsoft cho các thuộc tính được ghi lại ở đây.

Đây là ví dụ từ trang được liên kết:

// declspec_property.cpp
struct S {
   int i;
   void putprop(int j) { 
      i = j;
   }

   int getprop() {
      return i;
   }

   __declspec(property(get = getprop, put = putprop)) int the_prop;
};

int main() {
   S s;
   s.the_prop = 5;
   return s.the_prop;
}

12

Không, C ++ không có khái niệm về thuộc tính. Mặc dù có thể hơi khó khăn khi định nghĩa và gọi getThis () hoặc setThat (value), nhưng bạn đang đưa ra một tuyên bố với người tiêu dùng về những phương thức đó rằng một số chức năng có thể xảy ra. Mặt khác, việc truy cập các trường trong C ++ cho người tiêu dùng biết rằng sẽ không có chức năng bổ sung hoặc không mong muốn nào xảy ra. Các thuộc tính sẽ làm cho điều này ít rõ ràng hơn vì truy cập thuộc tính thoạt nhìn có vẻ phản ứng như một trường, nhưng thực tế lại phản ứng như một phương thức.

Ngoài ra, tôi đang làm việc trong một ứng dụng .NET (một CMS rất nổi tiếng) để cố gắng tạo ra một hệ thống thành viên khách hàng. Do cách họ đã sử dụng các thuộc tính cho các đối tượng người dùng của họ, các hành động đã xảy ra mà tôi không lường trước được, khiến các triển khai của tôi thực thi theo những cách kỳ lạ bao gồm cả đệ quy vô hạn. Điều này là do các đối tượng người dùng của họ đã thực hiện lệnh gọi đến lớp truy cập dữ liệu hoặc một số hệ thống bộ nhớ đệm toàn cầu khi cố gắng truy cập những thứ đơn giản như StreetAddress. Toàn bộ hệ thống của họ được thành lập dựa trên cái mà tôi gọi là lạm dụng tài sản. Nếu họ đã sử dụng các phương thức thay vì thuộc tính, tôi nghĩ rằng tôi sẽ nhận ra điều gì đang xảy ra nhanh hơn nhiều. Nếu họ đã sử dụng các trường (hoặc ít nhất là làm cho các thuộc tính của họ hoạt động giống các trường hơn), tôi nghĩ rằng hệ thống sẽ dễ dàng mở rộng và bảo trì hơn.

[Chỉnh sửa] Đã thay đổi suy nghĩ của tôi. Tôi đã có một ngày tồi tệ và hơi thất vọng. Việc dọn dẹp này nên chuyên nghiệp hơn.


11

Dựa trên https://stackoverflow.com/a/23109533/404734, đây là phiên bản có bộ cài công khai và bộ cài đặt riêng:

struct Foo
{
    class
    {
            int value;
            int& operator= (const int& i) { return value = i; }
            friend struct Foo;
        public:
            operator int() const { return value; }
    } alpha;
};

4

Đây không chính xác là một thuộc tính, nhưng nó thực hiện những gì bạn muốn theo cách đơn giản:

class Foo {
  int x;
public:
  const int& X;
  Foo() : X(x) {
    ...
  }
};

Ở đây, dấu X lớn hoạt động giống như public int X { get; private set; }trong cú pháp C #. Nếu bạn muốn các thuộc tính toàn diện, tôi đã thực hiện lần đầu tiên để triển khai chúng ở đây .


2
Đây không phải là ý tưởng tốt. Bất cứ khi nào bạn tạo một bản sao của một đối tượng của lớp này, thì tham chiếu Xcủa đối tượng mới sẽ vẫn trỏ đến thành viên của đối tượng cũ, vì nó chỉ được sao chép giống như một thành viên con trỏ. Bản thân điều này là không tốt, nhưng khi đối tượng cũ bị xóa, bạn sẽ bị hỏng bộ nhớ. Để thực hiện công việc này, bạn cũng sẽ phải triển khai hàm tạo bản sao, toán tử gán và hàm tạo di chuyển của riêng bạn.
toster

4

Bạn có thể biết điều đó nhưng tôi chỉ đơn giản làm như sau:

class Person {
public:
    std::string name() {
        return _name;
    }
    void name(std::string value) {
        _name = value;
    }
private:
    std::string _name;
};

Cách tiếp cận này rất đơn giản, không sử dụng thủ thuật thông minh và nó sẽ hoàn thành công việc!

Mặc dù vậy, vấn đề là một số người không thích đánh dấu gạch dưới trước các trường riêng tư của họ và vì vậy họ không thể thực sự sử dụng cách tiếp cận này, nhưng may mắn thay cho những người này, nó thực sự đơn giản. :)

Các tiền tố get và set không thêm rõ ràng cho API của bạn nhưng làm cho chúng dài dòng hơn và lý do tôi không nghĩ rằng họ thêm thông tin hữu ích là vì khi ai đó cần sử dụng một API nếu API có ý nghĩa thì cô ấy có thể sẽ nhận ra nó là gì không có tiền tố.

Một điều nữa, thật dễ hiểu rằng đây là những thuộc tính vì namekhông phải là một động từ.

Trường hợp xấu nhất, nếu các API nhất quán và người đó không nhận ra đó name()là trình truy cập và name(value)là kẻ đột biến thì cô ấy sẽ chỉ phải tra cứu nó một lần trong tài liệu để hiểu mẫu.

Tôi yêu C # nhiều như vậy, tôi không nghĩ C ++ cần thuộc tính gì cả!


Mutators của bạn có ý nghĩa nếu một công dụng foo(bar)(thay vì chậm hơn foo = bar), nhưng bộ truy xuất của bạn không có gì để làm với các thuộc tính ở tất cả ...
Matthias

@Matthias Nói rằng nó không liên quan gì đến thuộc tính cả, không cho tôi biết bất cứ điều gì, bạn có thể nói rõ hơn được không? ngoài ra tôi đã không cố gắng so sánh chúng nhưng nếu bạn cần một trình đột biến và truy cập, bạn có thể sử dụng quy ước này.
Eyal Solnik

Câu hỏi là về khái niệm phần mềm của Thuộc tính. Các thuộc tính có thể được sử dụng như thể chúng là thành viên dữ liệu công khai (cách sử dụng), nhưng chúng thực sự là các phương thức đặc biệt được gọi là trình truy cập (khai báo). Giải pháp của bạn tuân theo các phương thức (getters / setters thông thường) để khai báo và sử dụng. Vì vậy, trước hết, đây chắc chắn không phải là cách sử dụng OP đang yêu cầu, mà là một số quy ước đặt tên kỳ lạ và độc đáo (và do đó, thứ hai, cũng không có đường cú pháp).
Matthias

Là một tác dụng phụ nhỏ, trình đột biến của bạn đáng ngạc nhiên hoạt động như một Thuộc tính, vì trong C ++, tốt nhất nên khởi tạo với foo(bar)thay vì foo=barcó thể đạt được với void foo(Bar bar)phương thức trình đột biến trên một _foobiến thành viên.
Matthias

@Matthias Tôi biết thuộc tính là gì, tôi đã viết C ++ và C # khá nhiều trong hơn một thập kỷ, tôi không tranh cãi về lợi ích của thuộc tính và chúng là gì nhưng tôi chưa bao giờ THỰC SỰ cần chúng trong C ++, thực tế là bạn đang nói rằng chúng có thể được sử dụng làm dữ liệu công khai và điều đó hầu như đúng nhưng có những trường hợp trong C #, bạn thậm chí không thể sử dụng thuộc tính trực tiếp, chẳng hạn như chuyển một thuộc tính bằng cách tham chiếu trong khi với trường công khai, bạn có thể.
Eyal Solnik

4

Không .. Nhưng bạn nên cân nhắc nếu nó chỉ là hàm get: set và không có tác vụ bổ sung nào được định dạng sẵn bên trong các phương thức get: set chỉ đặt nó ở chế độ công khai.


2

Tôi đã thu thập các ý tưởng từ nhiều nguồn C ++ và đưa nó vào một ví dụ đẹp, vẫn khá đơn giản cho getters / setters trong C ++:

class Canvas { public:
    void resize() {
        cout << "resize to " << width << " " << height << endl;
    }

    Canvas(int w, int h) : width(*this), height(*this) {
        cout << "new canvas " << w << " " << h << endl;
        width.value = w;
        height.value = h;
    }

    class Width { public:
        Canvas& canvas;
        int value;
        Width(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } width;

    class Height { public:
        Canvas& canvas;
        int value;
        Height(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } height;
};

int main() {
    Canvas canvas(256, 256);
    canvas.width = 128;
    canvas.height = 64;
}

Đầu ra:

new canvas 256 256
resize to 128 256
resize to 128 64

Bạn có thể kiểm tra trực tuyến tại đây: http://codepad.org/zosxqjTX


Có một chi phí bộ nhớ để giữ cho việc tự tái tạo, + một cú pháp ctor khó hiểu.
Red.Wave

Để đề xuất tài sản? Tôi đoán đã có một loạt các đề xuất như vậy bị từ chối.
Red.Wave

@ Red.Wave cúi đầu trước chúa tể và bậc thầy của sự từ chối. Chào mừng bạn đến với C ++. Clang và MSVC có phần mở rộng tùy chỉnh cho các thuộc tính, nếu bạn không muốn các tham chiếu tự.
lama12345

Tôi không bao giờ có thể cúi đầu. Tôi nghĩ không phải mọi tính năng đều phù hợp. Đối với tôi, Đối tượng không chỉ là một cặp hàm setter + getter. Tôi đã thử triển khai riêng để tránh chi phí bộ nhớ vĩnh viễn không cần thiết, nhưng cú pháp và công việc khai báo các trường hợp không thỏa đáng; Tôi đã bị thu hút bởi việc sử dụng macro khai báo, nhưng dù sao thì tôi cũng không phải là một người yêu thích macro. Và cách tiếp cận của tôi cuối cùng đã dẫn đến các phần tử được truy cập bằng cú pháp hàm, mà nhiều người bao gồm cả tôi không chấp thuận.
Red.Wave

0

Lớp bạn có thực sự cần thực thi một số bất biến hay chỉ là một nhóm hợp lý của các phần tử thành viên? Nếu đó là cái thứ hai, bạn nên cân nhắc việc tạo một cấu trúc và truy cập trực tiếp vào các thành viên.


0

Có một tập hợp các macro được viết ở đây . Nó có các khai báo thuộc tính thuận lợi cho các loại giá trị, loại tham chiếu, loại chỉ đọc, loại mạnh và yếu.

class MyClass {

 // Use assign for value types.
 NTPropertyAssign(int, StudentId)

 public:
 ...

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.