2010-06-16 33 views
5

Trong mã bên dưới, có hai lệnh "tương đương" với std::for_each sử dụng các biểu thức boost:bind. Biên dịch dòng được chỉ định, dòng thất bại được chỉ định không thành công. Lời giải thích tốt nhất tôi có thể tìm thấy trong số tiền tiêu chuẩn để "bởi vì chúng tôi đã nói như vậy". Tôi đang tìm "lý do tại sao tiêu chuẩn cho biết hành vi này". Giả định của tôi là dưới đây.boost :: liên kết với các thành viên và ngữ cảnh được bảo vệ

Câu hỏi của tôi đơn giản là: Tại sao biên dịch dòng được chỉ định và dòng tương ứng không biên dịch (và tôi không muốn vì "tiêu chuẩn nói như vậy", tôi đã biết điều đó - tôi sẽ không chấp nhận bất kỳ câu trả lời nào đưa ra giải thích này; Tôi muốn giải thích là lý do tại sao tiêu chuẩn nói như vậy).

Ghi chú: Mặc dù tôi sử dụng tăng, tăng không liên quan đến câu hỏi này và lỗi ở nhiều định dạng khác nhau đã được sao chép bằng g ++ 4.1. * Và VC7.1.

#include <boost/bind.hpp> 
#include <iostream> 
#include <map> 
#include <algorithm> 

class Base 
{ 
protected: 
     void foo(int i) 
     { std::cout << "Base: " << i << std::endl; } 
}; 

struct Derived : public Base 
{ 
     Derived() 
     { 
       data[0] = 5; 
       data[1] = 6; 
       data[2] = 7; 
     } 

     void test() 
     { 
       // Compiles 
       std::for_each(data.begin(), data.end(), 
         boost::bind(&Derived::foo, this, 
           boost::bind(&std::map<int, int>::value_type::second, _1))); 

       // Fails to compile - why? 
       std::for_each(data.begin(), data.end(), 
         boost::bind(&Base::foo, this, 
           boost::bind(&std::map<int, int>::value_type::second, _1))); 
     } 

     std::map<int, int> data; 
}; 

int main(int, const char**) 
{ 
     Derived().test(); 

     return 0; 
} 

Dòng chỉ định thất bại với lỗi này: main.c: Trong chức năng thành viên 'khoảng trống nguồn gốc :: test()': main.c: 9: lỗi: 'khoảng trống cơ sở :: foo (int) 'được bảo vệ chính.C: 31: lỗi: trong ngữ cảnh này

Như đã nói, tuyên bố tương đương được cho là ở trên biên dịch sạch (và nếu tuyên bố vi phạm được nhận xét, chạy với kết quả mong đợi của việc in "5" , “6”, “7” trên các dòng riêng biệt).

Trong khi tìm kiếm một lời giải thích, tôi tình cờ gặp 11.5.1 trong tiêu chuẩn (cụ thể là, tôi nhìn vào dự thảo 2006/11/06):

An additional access check beyond those described earlier in clause 11 is applied when a non-static data member or nonstatic member function is a protected member of its naming class (11.2)105) As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (5.3.1), the nested-name-specifier shall name C or a class derived from C. All other accesses involve a (possibly implicit) object expression (5.2.5). In this case, the class of the object expression shall be C or a class derived from C.

Sau khi đọc bài viết này, nó trở thành rõ ràng tại sao câu lệnh thứ hai thất bại trong khi lần đầu tiên thành công, nhưng sau đó câu hỏi xuất hiện: Lý do cho điều này là gì? Ý tưởng ban đầu của tôi là trình biên dịch đã mở rộng các mẫu :: bound bind, phát hiện ra rằng Base :: foo đã được bảo vệ và khởi động nó bởi vì boost :: bind <…> không phải là một người bạn. Nhưng, tôi càng nghĩ về giải thích này, nó càng ít ý nghĩa, bởi vì nếu tôi nhớ chính xác, ngay khi bạn đưa con trỏ tới một thành viên (giả sử ban đầu bạn nằm trong sự kiểm soát truy cập của thành viên), mọi thông tin điều khiển truy cập là bị mất (tức là tôi có thể định nghĩa một hàm trả về một con trỏ tùy ý cho một thành viên mà trả về một thành viên công cộng, được bảo vệ hoặc riêng tư tùy thuộc vào một số đầu vào và người trả về sẽ không phải là người khôn ngoan hơn).

Tôi đã nghĩ về nó, và lời giải thích hợp lý duy nhất tôi có thể đưa ra lý do tại sao nó nên tạo sự khác biệt là trong trường hợp thừa kế nhiều lần. Cụ thể, tùy thuộc vào cách bố trí lớp, con trỏ thành viên khi được tính từ Cơ sở sẽ khác với con số được tính từ Nguồn gốc.

+0

Tôi sẽ lưu ý ngay: điều này có thể cần một chủ đề tốt hơn. Nếu bất cứ ai có thể cho tôi một cái tốt hơn, tôi sẽ thay đổi nó. Cảm ơn trước. –

+1

Vì tiêu chuẩn nói như vậy. –

+1

Có vẻ như 'Base :: foo' phải có' virtual'. Nó có hoạt động nếu bạn thêm nó vào không? – zildjohn01

Trả lời

5

Đó là tất cả về "ngữ cảnh". Trong lần gọi đầu tiên, ngữ cảnh của cuộc gọi là Derived có quyền truy cập vào các thành viên được bảo vệ Base và do đó được phép lấy địa chỉ của chúng. Trong bối cảnh thứ hai là "bên ngoài" Derived và do đó bên ngoài của Base để cho phép truy cập thành viên được bảo vệ không được phép.

1

Thực ra, điều này có vẻ hợp lý. Thừa kế cho phép bạn truy cập vào Derived :: foo và không phải Base :: foo. Hãy để tôi minh họa bằng ví dụ về mã:

struct Derived : public Base 
{ 
    void callPrivateMethod(Base &b) 
    { 
     // this should obviously fail 
     b.foo(5); 

     // pointer-to-member call should also fail 
     void (Base::*pBaseFoo) (int) = &Base::foo; // the same error as yours here 
     (b.*pBaseFoo)(5); 
    } 
}; 
0

Lý do cho hạn chế này là bắt buộc kiểm soát truy cập trên các lớp khác nhau dùng chung cơ sở.

này được củng cố bởi các ghi chú trong Core Language Defects Report defect #385, phần có liên quan sao chép ở đây để tham khảo:

[...] the reason we have this rule is that C 's use of inherited protected members might be different from their use in a sibling class, say D . Thus members and friends of C can only use B::p in a manner consistent with C 's usage, i.e., in C or derived-from- C objects.

Như một ví dụ về một cái gì đó nguyên tắc này ngăn cản:

class B { 
protected: 
    void p() { }; 
}; 

class C : public B { 
public: 
    typedef void (B::*fn_t)(); 
    fn_t get_p() { 
     return &B::p; // compilation error here, B::p is protected 
    } 
}; 

class D : public B { }; 

int main() { 
    C c; 
    C::fn_t pbp = c.get_p(); 
    B * pb = new D(); 
    (pb->*pbp)(); 
} 

Tình trạng bảo vệ của D::p là một cái gì đó chúng tôi muốn trình biên dịch thực thi, nhưng nếu việc biên dịch ở trên sẽ không xảy ra.

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