2011-10-28 38 views
7

Gần đây, tôi tìm thấy một cuộc thảo luận thú vị về cách cho phép truy cập chỉ đọc cho các thành viên tư nhân mà không Obfuscating thiết kế với nhiều thu khí, và là một trong những gợi ý là để làm điều đó theo cách này:const tham chiếu thành viên công khai cho thành viên lớp học tư nhân - tại sao nó hoạt động?

#include <iostream> 

class A { 
public: 
    A() : _ro_val(_val) {} 
    void doSomething(int some_val) { 
    _val = 10*some_val; 
    } 
    const int& _ro_val; 
private: 
    int _val; 
}; 

int main() { 
    A a_instance; 
    std::cout << a_instance._ro_val << std::endl; 
    a_instance.doSomething(13); 
    std::cout << a_instance._ro_val << std::endl; 
} 

Output:

$ ./a.out 
0 
130 

GotW#66 nêu rõ rằng cuộc đời của đối tượng bắt đầu

khi constructor của nó hoàn tất thành công và lợi nhuận cũng không mally. Tức là, điều khiển đến cuối thân của hàm tạo hoặc một câu lệnh trả về sớm hơn.

Nếu vậy, chúng tôi không đảm bảo rằng người ghi _val sẽ được tạo đúng cách khi chúng tôi thực hiện _ro_val(_val). Vì vậy, làm thế nào đến các mã trên hoạt động? Đó là hành vi không xác định? Hoặc là các kiểu nguyên thủy được cấp một số ngoại lệ cho tuổi thọ của đối tượng?

Bất cứ ai có thể chỉ cho tôi một số tham chiếu sẽ giải thích những điều đó?

+3

Nếu bạn đang lo lắng về điều này, bạn chỉ cần khai báo '_val' trước tiên trong định nghĩa lớp của bạn. Các thành viên được khởi tạo theo thứ tự mà chúng được khai báo. (Nhưng như câu trả lời nói, bạn không cần đối tượng được khởi tạo để tạo thành một tham chiếu đến nó.) –

+2

Hãy nhớ rằng điều này sẽ làm tăng kích thước của đối tượng và yêu cầu thêm một hướng để truy cập vào thành viên, trong khi một getter nội tuyến sẽ có hiệu quả như truy cập trực tiếp vào thành viên. Nó cũng tấn công tôi như là obfuscated hơn là một getter (kể từ khi bạn phải nhìn vào định nghĩa constructor để tìm ra những gì nó đề cập đến), nhưng đó chỉ là ý kiến ​​của tôi. –

+1

Nếu bạn thực sự thấy rằng getters và setters của bạn là "obfuscating thiết kế", nó có thể là có cái gì đó sai trong thiết kế của bạn. Bạn có thể đang sử dụng kiểu chống đối của Thiên Chúa. Xem http://stackoverflow.com/questions/565095/java-are-getters-and-setters-evil/565158#565158 – Raedwald

Trả lời

4

Trước khi hàm tạo được gọi là bộ nhớ thích hợp được dành riêng cho đối tượng trên Freestore (nếu bạn sử dụng new) hoặc trên ngăn xếp nếu bạn tạo đối tượng trên bộ nhớ cục bộ. Điều này ngụ ý rằng bộ nhớ cho _val đã được phân bổ theo thời gian bạn giới thiệu nó trong danh sách bộ khởi tạo thành viên, chỉ có bộ nhớ này chưa được khởi tạo đúng cách.

_ro_val(_val) 

Làm cho các thành viên tham khảo _ro_val tham khảo bộ nhớ phân bổ cho _val, mà thực sự có thể chứa bất cứ điều gì vào thời điểm này của thời gian.

Hiện vẫn còn là một hành vi undefined trong chương trình của bạn bởi vì, Bạn rõ ràng nên khởi _val-0 (hoặc một số giá trị, bạn chọn) trong cơ thể constructor/Member Initializer List.The đầu ra 0 trong trường hợp này chỉ là bởi vì bạn là may mắn nó có thể cung cấp cho bạn một số giá trị khác kể từ khi _val bị bỏ cấm. Xem hành vi here trên gcc 4.3.4 minh họa UB.

Nhưng đối với Câu hỏi, Có thực sự hành vi là Được xác định rõ.

+1

Vì vậy, nếu tôi hiểu chính xác, toàn bộ bố cục bộ nhớ cho nơi đối tượng sẽ được lưu trữ được xác định trước chúng ta thậm chí còn tới được hàm tạo và điều này làm cho tham chiếu '_ro_val_' hợp lệ, đúng không? –

+0

@ dare: Đó là ý chính của nó, vâng. – Xeo

+0

@ dare2be: Vâng, đó là chính xác. –

1

Địa chỉ của đối tượng không thay đổi.

I.e. nó được xác định rõ.

Tuy nhiên, kỹ thuật được hiển thị chỉ là tối ưu hóa sớm. Bạn không tiết kiệm thời gian của người lập trình. Và với trình biên dịch hiện đại, bạn không tiết kiệm thời gian thực thi hoặc kích thước mã máy. Nhưng bạn làm cho các đối tượng không thể gán được.

Cheers & h.,

+0

"_premature optimization_" Tôi thà nói sự bi quan sớm. – curiousguy

0

Theo ý kiến ​​của tôi, nó là hợp pháp (được xác định rõ) để khởi tạo một tài liệu tham khảo với một đối tượng chưa được khởi tạo. Điều đó là hợp pháp nhưng tiêu chuẩn (tốt, bản thảo C++ 11 mới nhất, đoạn 8.5.3.3) khuyến sử dụng) đối tượng được xây dựng hoàn toàn hợp lệ (như là một initializer:

A reference shall be initialized to refer to a valid object or function.

Các câu tiếp theo từ cùng một đoạn ném ánh sáng hơn một chút ở việc tạo ra tài liệu tham khảo:

[Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior.]

Tôi hiểu rằng việc tạo tham chiếu có nghĩa là tham chiếu ràng buộc đối tượng thu được bằng cách bỏ qua con trỏ của nó và có thể giải thích rằng điều kiện tiên quyết tối thiểu để khởi tạo tham chiếu kiểu T & là có một địa chỉ của phần bộ nhớ dành riêng cho đối tượng kiểu T (được đặt trước, nhưng chưa được khởi tạo).

Truy cập đối tượng chưa được khởi tạo thông qua tham chiếu của nó có thể nguy hiểm.

Tôi đã viết một ứng dụng thử nghiệm đơn giản mà hiện khởi chiếu với đối tượng chưa được khởi tạo và hậu quả của việc tiếp cận đối tượng mà thông qua nó:

class C 
{ 
public: 
    int _n; 

    C() : _n(123) 
    { 
     std::cout << "C::C(): _n = " << _n << " ...and blowing up now!" << std::endl;  
     throw 1; 
    } 
}; 

class B 
{ 
public: 

    // pC1- address of the reference is the address of the object it refers 
    // pC2- address of the object 
    B(const C* pC1, const C* pC2) 
    { 
     std::cout << "B::B(): &_ro_c = " << pC1 << "\n\t&_c = " << pC2 << "\n\t&_ro_c->_n = " << pC1->_n << "\n\t&_c->_n = " << pC2->_n << std::endl; 
    } 
}; 

class A 
{ 
    const C& _ro_c;  
    B _b; 
    C _c; 

public:  

    // Initializer list: members are initialized in the order how they are 
    // declared in class 
    // 
    // Initializes reference to _c 
    // 
    // Fully constructs object _b; its c-tor accesses uninitialized object 
    // _c through its reference and its pointer (valid but dangerous!) 
    // 
    // construction of _c fails! 
    A() : _ro_c(_c), _b(&_ro_c, &_c), _c() 
    { 
     // never executed 
     std::cout << "A::A()" << std::endl; 
    } 
}; 

int main() 
{ 
    try 
    { 
     A a; 
    } 
    catch(...) 
    { 
     std::cout << "Failed to create object of type A" << std::endl; 
    } 

    return 0; 
} 

Output:

B::B(): &_ro_c = 001EFD70 
     &_c = 001EFD70 
     &_ro_c->_n = -858993460 
     &_c->_n = -858993460 
C::C(): _n = 123 ...and blowing up now! 
Failed to create object of type A 
Các vấn đề liên quan