`Is_base_of` hoạt động như thế nào?


118

Đoạn mã sau hoạt động như thế nào?

typedef char (&yes)[1];
typedef char (&no)[2];

template <typename B, typename D>
struct Host
{
  operator B*() const;
  operator D*();
};

template <typename B, typename D>
struct is_base_of
{
  template <typename T> 
  static yes check(D*, T);
  static no check(B*, int);

  static const bool value = sizeof(check(Host<B,D>(), int())) == sizeof(yes);
};

//Test sample
class Base {};
class Derived : private Base {};

//Expression is true.
int test[is_base_of<Base,Derived>::value && !is_base_of<Derived,Base>::value];
  1. Lưu ý rằng đó Blà cơ sở tư nhân. Cái này hoạt động ra sao?

  2. Lưu ý rằng đó operator B*()là const. Tại sao nó lại quan trọng?

  3. Tại sao template<typename T> static yes check(D*, T);tốt hơn static yes check(B*, int);?

Lưu ý : Đây là phiên bản thu nhỏ (các macro bị loại bỏ) của boost::is_base_of. Và điều này hoạt động trên nhiều loại trình biên dịch.


4
Bạn sẽ rất bối rối khi sử dụng cùng một mã định danh cho một tham số mẫu và một tên lớp thực sự ...
Matthieu M.

1
@Matthieu M., tôi đã tự sửa sai :)
Kirill V. Lyadvinsky,

2
Cách đây một thời gian, tôi đã viết một triển khai thay thế của is_base_of: ideone.com/T0C1V Tuy nhiên, nó không hoạt động với các phiên bản GCC cũ hơn (GCC4.3 hoạt động tốt).
Johannes Schaub -

3
Được rồi, tôi sẽ đi dạo.
jokoon

2
Cách triển khai này không đúng. is_base_of<Base,Base>::valuenên được true; điều này trở lại false.
chengiz

Câu trả lời:


109

Nếu chúng có liên quan

Chúng ta hãy giả sử rằng đó Bthực sự là một cơ sở của D. Sau đó, đối với cuộc gọi đến check, cả hai phiên bản đều khả thi vì Hostcó thể được chuyển đổi thành D* B* . Đó là một chuỗi chuyển đổi do người dùng xác định như được mô tả bởi 13.3.3.1.2từ Host<B, D>đến D*B*tương ứng. Để tìm các hàm chuyển đổi có thể chuyển đổi lớp, các hàm ứng cử viên sau đây được tổng hợp cho checkhàm đầu tiên theo13.3.1.5/1

D* (Host<B, D>&)

Hàm chuyển đổi đầu tiên không phải là một ứng cử viên vì B*không thể chuyển đổi thành D*.

Đối với chức năng thứ hai, tồn tại các ứng cử viên sau:

B* (Host<B, D> const&)
D* (Host<B, D>&)

Đó là hai ứng cử viên chức năng chuyển đổi lấy đối tượng chủ. Đầu tiên lấy nó bằng tham chiếu const, và thứ hai thì không. Do đó, đối tượng thứ hai là đối sánh tốt hơn cho *thisđối tượng không phải const ( đối số đối tượng ngụ ý ) bởi 13.3.3.2/3b1sb4và được sử dụng để chuyển đổi thành hàm B*thứ hai check.

Nếu bạn xóa const, chúng tôi sẽ có các ứng cử viên sau

B* (Host<B, D>&)
D* (Host<B, D>&)

Điều này có nghĩa là chúng ta không thể chọn theo hằng số nữa. Trong một kịch bản giải quyết quá tải thông thường, lệnh gọi bây giờ sẽ không rõ ràng vì thông thường kiểu trả về sẽ không tham gia vào giải quyết quá tải. Đối với các chức năng chuyển đổi, tuy nhiên, có một cửa sau. Nếu hai hàm chuyển đổi tốt như nhau, thì kiểu trả về của chúng sẽ quyết định xem ai là tốt nhất theo 13.3.3/1. Vì vậy, nếu bạn loại bỏ const, thì đầu tiên sẽ được sử dụng, bởi vì B*chuyển đổi tốt B*hơn D*thành B*.

Bây giờ chuỗi chuyển đổi do người dùng xác định là tốt hơn? Một cho chức năng kiểm tra thứ hai hay đầu tiên? Quy tắc là các chuỗi chuyển đổi do người dùng xác định chỉ có thể được so sánh nếu họ sử dụng cùng một hàm chuyển đổi hoặc phương thức khởi tạo theo 13.3.3.2/3b2. Đây chính xác là trường hợp ở đây: Cả hai đều sử dụng hàm chuyển đổi thứ hai. Lưu ý rằng do đó, const rất quan trọng vì nó buộc trình biên dịch thực hiện chức năng chuyển đổi thứ hai.

Vì chúng ta có thể so sánh chúng - cái nào tốt hơn? Quy tắc là chuyển đổi tốt hơn từ kiểu trả về của hàm chuyển đổi thành kiểu đích sẽ thắng (một lần nữa bằng cách 13.3.3.2/3b2). Trong trường hợp này, D*chuyển đổi tốt D*hơn thành B*. Do đó, chức năng đầu tiên được chọn và chúng tôi nhận ra sự kế thừa!

Lưu ý rằng vì chúng ta không bao giờ cần thực sự chuyển đổi sang lớp cơ sở, do đó chúng ta có thể nhận ra kế thừa riêng vì liệu chúng ta có thể chuyển đổi từ a D*sang a B*không phụ thuộc vào hình thức kế thừa theo4.10/3

Nếu chúng không liên quan

Bây giờ hãy giả sử chúng không có quan hệ thừa kế. Vì vậy, đối với chức năng đầu tiên, chúng tôi có các ứng cử viên sau

D* (Host<B, D>&) 

Và lần thứ hai, chúng tôi có một bộ khác

B* (Host<B, D> const&)

Vì chúng ta không thể chuyển đổi D*thành B*nếu chúng ta không có mối quan hệ kế thừa, nên bây giờ chúng ta không có hàm chuyển đổi chung giữa hai chuỗi chuyển đổi do người dùng xác định! Vì vậy, chúng ta sẽ mơ hồ nếu không vì thực tế là hàm đầu tiên là một mẫu. Mẫu là lựa chọn thứ hai khi có một chức năng không phải mẫu cũng tốt theo 13.3.3/1. Do đó, chúng tôi chọn hàm không phải mẫu (hàm thứ hai) và chúng tôi nhận ra rằng không có sự kế thừa giữa BD!


2
Ah! Andreas đã có đoạn văn đúng, quá tệ là anh ấy đã không đưa ra câu trả lời như vậy :) Cảm ơn bạn đã dành thời gian, tôi ước tôi có thể yêu thích nó.
Matthieu M.

2
Đây sẽ là câu trả lời yêu thích của tôi từ trước đến nay ... một câu hỏi: bạn đã đọc toàn bộ tiêu chuẩn C ++ chưa hay bạn chỉ làm việc trong ủy ban C ++ ?? Xin chúc mừng!
Marco A.

4
@DavidKernin làm việc trong C ++ committe không tự động khiến bạn biết C ++ hoạt động như thế nào :) Vì vậy, bạn chắc chắn phải đọc phần Tiêu chuẩn cần thiết để biết chi tiết, phần mà tôi đã thực hiện. Chưa đọc tất cả, vì vậy tôi chắc chắn không thể giúp gì với hầu hết các câu hỏi liên quan đến Thư viện tiêu chuẩn hoặc phân luồng :)
Johannes Schaub - litb

1
@underscore_d Công bằng mà nói, thông số kỹ thuật không cấm các đặc điểm std :: sử dụng một số phép thuật trình biên dịch để những người áp dụng thư viện tiêu chuẩn có thể sử dụng chúng mà họ thích . Họ sẽ tránh được những pha nhào lộn theo mẫu, điều này cũng giúp tăng tốc thời gian biên dịch và sử dụng bộ nhớ. Điều này đúng ngay cả khi giao diện trông như thế nào std::is_base_of<...>. Tất cả đều nằm dưới mui xe.
Johannes Schaub - litb

2
Tất nhiên, các thư viện chung boost::cần đảm bảo rằng chúng có sẵn những nội dung này trước khi sử dụng. Và tôi có cảm giác có một số loại tâm lý "chấp nhận thử thách" trong số họ để thực hiện mọi thứ mà không cần sự trợ giúp của trình biên dịch :)
Johannes Schaub - litb

24

Hãy xem nó hoạt động như thế nào bằng cách xem các bước.

Bắt đầu với sizeof(check(Host<B,D>(), int()))một phần. Trình biên dịch có thể nhanh chóng thấy rằng đây check(...)là một biểu thức gọi hàm, vì vậy nó cần phải thực hiện giải quyết quá tải check. Có sẵn hai quá tải ứng viên, template <typename T> yes check(D*, T);no check(B*, int);. Nếu người đầu tiên được chọn, bạn sẽ nhận được sizeof(yes), nếu khôngsizeof(no)

Tiếp theo, hãy xem xét độ phân giải quá tải. Quá tải đầu tiên là một khởi tạo mẫu check<int> (D*, T=int)và ứng cử viên thứ hai là check(B*, int). Các đối số thực tế được cung cấp là Host<B,D>int(). Tham số thứ hai rõ ràng không phân biệt chúng; nó chỉ được dùng để làm cho quá tải đầu tiên trở thành một mẫu. Chúng ta sẽ xem phần sau tại sao phần mẫu lại có liên quan.

Bây giờ hãy xem các trình tự chuyển đổi cần thiết. Đối với quá tải đầu tiên, chúng tôi có Host<B,D>::operator D*- một chuyển đổi do người dùng xác định. Đối với thứ hai, quá tải phức tạp hơn. Chúng tôi cần một B *, nhưng có thể có hai chuỗi chuyển đổi. Một là thông qua Host<B,D>::operator B*() const. Nếu (và chỉ khi) B và D có quan hệ với nhau bằng sự kế thừa thì chuỗi chuyển đổi Host<B,D>::operator D*()+ sẽ D*->B*tồn tại. Bây giờ giả sử D thực sự kế thừa từ B. Hai chuỗi chuyển đổi là Host<B,D> -> Host<B,D> const -> operator B* const -> B*Host<B,D> -> operator D* -> D* -> B*.

Vì vậy, đối với B và D có liên quan, no check(<Host<B,D>(), int())sẽ không rõ ràng. Kết quả là, khuôn mẫu yes check<int>(D*, int)được chọn. Tuy nhiên, nếu D không kế thừa từ B, thì no check(<Host<B,D>(), int())không phải là mơ hồ. Tại thời điểm này, giải quyết quá tải không thể xảy ra dựa trên chuỗi chuyển đổi ngắn nhất. Tuy nhiên, với các chuỗi chuyển đổi bằng nhau, độ phân giải quá tải ưu tiên các hàm không phải mẫu, tức là no check(B*, int).

Bây giờ bạn thấy tại sao không quan trọng việc kế thừa là riêng tư: mối quan hệ đó chỉ phục vụ để loại bỏ no check(Host<B,D>(), int())khỏi giải quyết quá tải trước khi kiểm tra quyền truy cập xảy ra. Và bạn cũng thấy lý do tại sao operator B* constphải là const: khác thì không cần Host<B,D> -> Host<B,D> constbước, không mơ hồ và no check(B*, int)sẽ luôn được chọn.


Giải thích của bạn không giải thích cho sự hiện diện của const. Nếu câu trả lời của bạn là đúng thì không constcần. Nhưng nó không phải là sự thật. Loại bỏ constvà lừa sẽ không hoạt động.
Alexey Malistov

Nếu không có hằng số, hai chuỗi chuyển đổi no check(B*, int)không còn mơ hồ.
MSalters

Nếu bạn chỉ để lại no check(B*, int), sau đó cho liên quan BD, nó sẽ không mơ hồ. Trình biên dịch sẽ chọn operator D*()thực hiện chuyển đổi một cách rõ ràng vì nó không có hằng số. Đó là thay vì một chút theo hướng ngược lại: Nếu bạn loại bỏ các const, bạn giới thiệu một số cảm giác mơ hồ, nhưng đó đã được giải quyết bởi thực tế là operator B*()cung cấp một kiểu trả về vượt trội mà không cần chuyển đổi con trỏ đến B*như D*vậy.
Johannes Schaub -

Đó thực sự là điểm mấu chốt: sự không rõ ràng nằm giữa hai chuỗi chuyển đổi khác nhau để lấy a B*từ <Host<B,D>()tạm thời.
MSalters

Đây là một câu trả lời tốt hơn. Cảm ơn! Vì vậy, như tôi đã hiểu, nếu một chức năng tốt hơn, nhưng không rõ ràng, thì một chức năng khác được chọn?
dùng1289

4

Các privatebit được hoàn toàn bị bỏ qua bởi is_base_ofvì độ phân giải quá tải xảy ra trước khi kiểm tra khả năng tiếp cận.

Bạn có thể xác minh điều này một cách đơn giản:

class Foo
{
public:
  void bar(int);
private:
  void bar(double);
};

int main(int argc, char* argv[])
{
  Foo foo;
  double d = 0.3;
  foo.bar(d);       // Compiler error, cannot access private member function
}

Điều tương tự cũng áp dụng ở đây, thực tế Blà cơ sở tư nhân không ngăn cản việc kiểm tra diễn ra, nó sẽ chỉ ngăn chặn việc chuyển đổi, nhưng chúng tôi không bao giờ yêu cầu chuyển đổi thực sự;)


Sắp xếp. Không có chuyển đổi cơ sở nào được thực hiện cả. hostđược chuyển đổi tùy ý thành D*hoặc B*trong biểu thức không được đánh giá. Vì một số lý do, D*được ưu tiên hơn B*trong những điều kiện nhất định.
Potatoswatter

Tôi nghĩ câu trả lời là ở 13.3.1.1.2 nhưng tôi vẫn chưa sắp xếp chi tiết :)
Andreas Brinck

Câu trả lời của tôi chỉ giải thích phần "tại sao ngay cả các công việc tư nhân", câu trả lời của sellibitze chắc chắn đầy đủ hơn mặc dù tôi đang háo hức chờ đợi lời giải thích rõ ràng về quy trình giải quyết đầy đủ tùy theo từng trường hợp.
Matthieu M.

2

Nó có thể liên quan đến độ phân giải quá tải wrt sắp xếp một phần. D * chuyên biệt hơn B * trong trường hợp D xuất phát từ B.

Các chi tiết chính xác là khá phức tạp. Bạn phải tìm ra các tiền lệ của các quy tắc giải quyết quá tải khác nhau. Đặt hàng từng phần là một. Độ dài / loại trình tự chuyển đổi là một thứ khác. Cuối cùng, nếu hai chức năng khả thi được coi là tốt như nhau, các chức năng không phải là khuôn mẫu được chọn thay vì các mẫu chức năng.

Tôi chưa bao giờ cần phải tra cứu cách các quy tắc này tương tác. Nhưng có vẻ như thứ tự một phần đang chi phối các quy tắc giải quyết quá tải khác. Khi D không xuất phát từ B, các quy tắc sắp xếp từng phần không được áp dụng và mẫu không phải là mẫu hấp dẫn hơn. Khi D xuất phát từ B, thứ tự từng phần bắt đầu và làm cho mẫu hàm hấp dẫn hơn - có vẻ như vậy.

Đối với việc thừa kế là riêng tư: mã không bao giờ yêu cầu chuyển đổi từ D * sang B * mà sẽ yêu cầu kế thừa công khai.


Tôi nghĩ nó giống như vậy, tôi nhớ đã xem một cuộc thảo luận rộng rãi trên các kho lưu trữ tăng cường về việc triển khai is_base_ofvà các vòng lặp mà những người đóng góp đã trải qua để đảm bảo điều này.
Matthieu M.

The exact details are rather complicated- đó là điểm. Vui lòng giải thích. Tôi muốn biết.
Alexey Malistov

@Alexey: Chà, tôi nghĩ tôi đã chỉ bạn đi đúng hướng. Kiểm tra cách các quy tắc giải quyết quá tải khác nhau tương tác trong trường hợp này. Sự khác biệt duy nhất giữa D xuất phát từ B và D không xuất phát từ B đối với việc giải quyết trường hợp quá tải này là quy tắc sắp xếp từng phần. Giải quyết quá tải được mô tả trong §13 của tiêu chuẩn C ++. Bạn có thể có được một dự thảo miễn phí: open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1804.pdf
sellibitze

Giải quyết quá tải kéo dài 16 trang trong bản nháp đó. Tôi đoán, nếu bạn thực sự cần hiểu các quy tắc và sự tương tác giữa chúng đối với trường hợp này, bạn nên đọc phần hoàn chỉnh §13.3. Tôi sẽ không tin vào việc nhận được một câu trả lời ở đây là đúng 100% và theo tiêu chuẩn của bạn.
sellibitze

xin vui lòng xem câu trả lời của tôi để được giải thích về nó nếu bạn quan tâm.
Johannes Schaub -

0

Tiếp theo câu hỏi thứ hai của bạn, hãy lưu ý rằng nếu không có const, Host sẽ không được tạo ra nếu được khởi tạo bằng B == D. Nhưng is_base_of được thiết kế để mỗi lớp là một cơ sở của chính nó, do đó một trong các toán tử chuyển đổi phải là const.

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.