2015-03-26 15 views
30

tôi stumbled trên this question, trong đó có một câu trả lời rằng đã sử dụng một cấu trúc kỳ lạ:Trình điều khiển. * & Làm gì?

typedef std::queue<int> Q; 
typedef Q::container_type C; 

C & get (Q &q) 
{ 
    struct hack : private Q { 
     static C & get (Q &q) { 
      return q.*&hack::c; 
     } 
    }; 
    return hack::get(q); 
} 

tôi thường làm theo mà q có quyền truy cập vào c thành viên của mình đang được tham chiếu bởi get chức năng. Nhưng, tôi đang ở một mất mát để giải thích rõ ràng nó. Điều gì đang xảy ra chính xác với .*& và tại sao nó được phép?

+9

Đó là hai toán tử: '. *' Và '&'. – chris

+6

Tôi nghĩ rằng mẹo có thể đã được lấy cảm hứng từ http://stackoverflow.com/a/1065606/962089 – chris

+1

@chris Đó là một phát hiện mang tính phân biệt. Cảm ơn vì điều này! –

Trả lời

26
typedef std::queue<int> Q; 

Qqueue vùng chứa thích hợp.

typedef Q::container_type C; 

C là container cơ bản của Q - mà là một deque<int>.

C & get (Q &q) { 

get mất một queue và trả về một deque. Trong thực tế nó trả về deque rằng các kết thúc queue: bằng phương tiện thông thường, điều này là không thể.

struct hack : private Q { 

hack là loại cục bộ cho hàm. Nó kế thừa từ Q và chỉ có một hàm thành viên tĩnh. Từ tên của nó, bạn có thể nghi ngờ nó là một hack. Bạn đúng rồi.

Không hack là bao giờ được khởi tạo.

static C & get (Q &q) { 

hack::get có cùng chữ ký với số get. Trong thực tế, chúng tôi ủy nhiệm tất cả công việc của get cho phương pháp này.

 return q.*&hack::c; 

dòng này cần được chia nhỏ. Tôi sẽ làm điều đó trong nhiều dòng:

 using mem_ptr_t = C Q::*; // aka typedef C Q::*mem_ptr_t; 
     mem_ptr_t c_mem_ptr = &hack::c; 
     C& ret = q.*c_mem_ptr; 
     return ret; 

Dòng đầu tiên xác định loại của một con trỏ thành viên đến một lĩnh vực loại C trong một Q. Cả C++ 11 và C++ 03 cách đặt tên kiểu này đều xấu.

Dòng thứ hai là con trỏ thành viên đến trường c trong Q. Nó thực hiện điều này thông qua các lỗ hổng trong hệ thống kiểu của C++. &hack::c là hợp lý của loại C hack::* - một con trỏ đến một thành viên thuộc loại C trong một loại thuộc loại hack. Thực tế, đó là lý do tại sao chúng tôi có thể truy cập nó trong một thành viên static của hack. Nhưng c được đề cập thực sự là trong Q, do đó loại thực tế của biểu thức trong C++ là C Q::*: một con trỏ đến biến thành viên là Q.

Bạn không thể trực tiếp nhận con trỏ thành viên này trong phạm vi hack - &Q::c là bất hợp pháp, nhưng &hack::c thì không.

Bạn có thể nghĩ đến con trỏ thành viên là 'offsets gõ' vào loại khác: &hack::c là "bù đắp" của c trong Q cùng với biết nó là loại C. Bây giờ điều này không thực sự đúng - đó là một số giá trị mờ cho trình biên dịch biết cách lấy c từ Q - nhưng nó giúp nghĩ về nó theo cách đó (và nó có thể được thực hiện theo cách đó trong các trường hợp đơn giản).

Sau đó, chúng tôi sử dụng con trỏ thành viên này cùng với Q& để lấy số c ra khỏi số Q. Bắt một con trỏ thành viên bị ràng buộc bởi bảo vệ: sử dụng nó không phải là! Cách chúng tôi thực hiện với nhà điều hành .*, là nhà điều hành dereference thành viên, bạn có thể chuyển các con trỏ chức năng thành viên hoặc thành viên ở bên phải và các phiên bản lớp ở bên trái.

instance .* member_ptr là cụm từ tìm thấy thành viên "được trỏ tới" bởi member_ptr trong số instance. Trong mã ban đầu, mọi thứ được thực hiện trên một dòng:

instance .* &class_name::member_name 

vì vậy có vẻ như có một nhà điều hành .*&.

} 
    }; 

và sau đó chúng tôi đóng lên các phương pháp tĩnh và hack lớp, và:

return hack::get(q); 
} 

gọi nó. Kỹ thuật này cho phép truy cập vào trạng thái protected: không có nó, protected thành viên chỉ có thể được truy cập trong các lớp con của cùng một trường hợp. Sử dụng điều này, chúng tôi có thể truy cập protected thành viên của bất kỳ trường hợp nào mà không vi phạm bất kỳ tiêu chuẩn nào.

+2

Tôi nhận thấy bạn không bao giờ đưa ra loại 'c_mem_ptr', mặc dù đó là chìa khóa cho toàn bộ mẹo (và vi phạm lý thuyết kiểu - nó phải là' C & hack :: * ') –

+2

@BenVoigt: Holly cow, đó là một nhận xét tinh tế. Cảm ơn vì cái nhìn sâu sắc đó. Tôi đã bỏ lỡ điều này từ câu trả lời được tham chiếu: * Điểm mấu chốt là loại con trỏ thành viên bị ràng buộc với lớp thực sự chứa thành viên đó - không phải lớp bạn đã chỉ định khi lấy địa chỉ. * – jxh

+0

@BenVoigt: Không, đó là CQ :: *. – Jamboree

8

Đó là một hack, như danh pháp chỉ ra.

.* lấy một đối tượng ở bên trái và con trỏ thành viên ở bên phải và giải quyết thành viên được chỉ định của đối tượng đã cho. & là, tất nhiên, các nhà điều hành tham chiếu; &Class::Member trả về một con trỏ thành viên, không thể tự hủy đăng ký nhưng có thể được sử dụng với các toán tử .*->* (sau cùng là toán tử wackiest của tất cả các toán tử C++). Vì vậy, obj .* &Class::Member có cùng một hiệu ứng obj.Member.

Lý do phiên bản phức tạp hơn này đang được sử dụng đi xuống một lỗ hổng trong ngữ nghĩa bảo vệ; về cơ bản, nó cho phép truy cập vào protected thành viên của một đối tượng lớp cơ sở, ngay cả khi đối tượng không cùng loại với lớp làm việc này.

Cá nhân, tôi nghĩ mẹo này quá thông minh một nửa. Tôi thường * viết mã như sau:

struct hack : private Q { 
    static C & get (Q &q) { 
     return static_cast<hack &>(q).c; 
    } 
}; 

Kỹ thuật ít an toàn hơn nhưng không che khuất những gì đang diễn ra.

. * Vâng, thông thường tôi sẽ tránh viết một thứ như vậy. Nhưng tôi đã làm điều này sớm hơn, vì vậy tôi không thể ném đá.

+7

Tôi sẽ không gọi UB "hơi kém an toàn". –

+0

@ T.C. Tôi sẽ, vì không có trình biên dịch thực sự làm điều gì đó bất ngờ trong trường hợp đó. Điều duy nhất phải lo lắng là việc kiểm soát tương lai, mặc dù rất khó để xem cách trình biên dịch nào có thể hoạt động khác nhau. – Sneftel

+0

Phiên bản 'kém an toàn' của bạn dựa trên các lớp cha được lưu trữ dưới dạng tiền tố và/hoặc con không thêm phần tử nào trước đó. Điều này chỉ được đảm bảo cho các loại bố cục chuẩn: 'hack' của bạn có thể hoặc không thể là bố cục chuẩn. Khi một loại không phải là bố trí chuẩn, trình biên dịch được tự do (nói) định vị lại các yếu tố dựa trên hướng dẫn hồ sơ, chèn dữ liệu lược tả có kích thước khác nhau ở mặt trước của hack và Q khác nhau về kích thước, thêm dữ liệu canary để phát hiện UB, hoặc toàn bộ vô số thứ khác mà mã của bạn sẽ phá vỡ. – Yakk

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