2013-01-24 50 views
10

Tôi muốn bind() đến phiên bản lớp cơ sở của hàm của lớp từ lớp dẫn xuất. Chức năng được đánh dấu được bảo vệ trong cơ sở. Khi tôi làm như vậy, mã biên dịch vui vẻ trong Clang (Apple LLVM Compiler 4.1) nhưng đưa ra một lỗi trong cả g ++ 4.7.2 và trong Visual Studio 2010. Lỗi này nằm dọc theo dòng: "'Base :: foo': không thể truy cập thành viên được bảo vệ. "std :: bind() - ing một hàm thành viên được bảo vệ cơ sở từ hàm thành viên của lớp dẫn xuất

Ý nghĩa là ngữ cảnh cho tham chiếu thực sự nằm trong phạm vi bind(), trong đó tất nhiên chức năng được xem là được bảo vệ. Nhưng không nên bind() kế thừa ngữ cảnh của chức năng gọi - trong trường hợp này là Derived::foo() - và do đó xem phương thức cơ sở là có thể truy cập?

Chương trình sau minh họa sự cố.

struct Base 
{ 
protected: virtual void foo() {} 
}; 

struct Derived : public Base 
{ 
protected: 
    virtual void foo() override 
    { 
     Base::foo();      // Legal 

     auto fn = std::bind(&Derived::foo, 
      std::placeholders::_1);  // Legal but unwanted. 
     fn(this); 

     auto fn2 = std::bind(&Base::foo, 
      std::placeholders::_1);  // ILLEGAL in G++ 4.7.2 and VS2010. 
     fn2(this); 
    } 
}; 

Tại sao có sự khác biệt về hành vi? Đó là chính xác? Giải pháp nào có sẵn cho các trình biên dịch lỗi?

+0

Có chủ ý là 'Derived :: foo' tự gọi, hay chỉ là kết quả của việc đơn giản hóa một ví dụ? – aschepler

+0

@aschepler Đó là phần "không mong muốn" của "hợp pháp nhưng không mong muốn". – OldPeculier

Trả lời

9

trả lời: xem boost::bind with protected members & context mà trích dẫn phần này của Standard

Một kiểm tra truy cập bổ sung ngoài những mô tả ở trên tại khoản 11 được áp dụng khi một thành viên dữ liệu không tĩnh hay không tĩnh hàm thành viên là một bảo vệ thành viên của lớp đặt tên (11.2) 105) Như được mô tả trước đó, quyền truy cập vào thành viên được bảo vệ được cấp vì tham chiếu xảy ra ở một người bạn hoặc thành viên của một số lớp C. Nếu truy cập được tạo thành con trỏ đến thành viên (5.3 .1), tên lồng nhau-specifier sẽ đặt tên C hoặc ac lass bắt nguồn từ C. Tất cả các truy cập khác liên quan đến biểu thức đối tượng (có thể là ) (5.2.5). Trong trường hợp này, lớp của biểu thức đối tượng sẽ là C hoặc một lớp học có nguồn gốc từ C.

Cách giải quyết: làm foo một hàm public viên

#include <functional> 

struct Base 
{ 
public: virtual void foo() {} 
}; 
+3

Nếu bạn không thể thay đổi 'Base', hãy điều chỉnh nó bằng phương thức Derived (ví dụ' void basefoo() {Base :: foo();} '). Đáng ngạc nhiên và gây nhầm lẫn trong Visual C++ (Express 2010), bạn thực sự có thể sử dụng '& Derived :: Base :: foo' để bỏ qua lỗi này nhưng về cơ bản bỏ qua đối số thứ hai của bạn (tức là nó luôn sử dụng' this'). –

9

này không có gì để làm với bind. Bởi vì phần của tiêu chuẩn @rhalbersma đã được trích dẫn, biểu thức &Base::foo là bất hợp pháp trong một thành viên không phải là bạn của Derived, trong mọi ngữ cảnh.

Nhưng nếu ý định của bạn là làm điều gì đó tương đương với gọi Base::foo();, bạn có vấn đề lớn hơn: con trỏ tới hàm thành viên luôn là gọi ghi đè ảo.

#include <iostream> 

class B { 
public: 
    virtual void f() { std::cout << "B::f" << std::endl; } 
}; 

class D : public B { 
public: 
    virtual void f() { std::cout << "D::f" << std::endl; } 
}; 

int main() { 
    D d; 
    d.B::f(); // Prints B::f 

    void (B::*ptr)() = &B::f; 
    (d.*ptr)(); // Prints D::f! 
} 
Các vấn đề liên quan