2017-06-11 62 views
11

xem xét lớp phân cấp này:Tại sao không trình biên dịch C++ tối ưu hóa dynamic_cast này từ một lớp học cuối cùng?

struct Animal { virtual ~Animal(); }; 
struct Cat : virtual Animal {}; 
struct Dog final : virtual Animal {}; 

sự hiểu biết của tôi là đưa final trên class Dog đảm bảo rằng không ai có thể tạo ra một lớp kế thừa từ Dog, trong đó, hệ quả tất yếu, có nghĩa là không ai có thể tạo ra một lớp mà LÀ-A Dog và IS-A Cat cùng một lúc.

Hãy xem xét hai dynamic_cast s này:

Dog *to_final(Cat *c) { 
    return dynamic_cast<Dog*>(c); 
} 

Cat *from_final(Dog *d) { 
    return dynamic_cast<Cat*>(d); 
} 

GCC, ICC, và MSVC bỏ qua final vòng loại và tạo ra một cuộc gọi đến __dynamic_cast; điều này thật đáng tiếc nhưng không đáng ngạc nhiên.

gì làm tôi ngạc nhiên là Clang và Zapcc cả generate mã tối ưu cho from_final ("luôn luôn trả nullptr"), nhưng tạo ra một cuộc gọi đến __dynamic_cast cho to_final.

Đây có phải là thực sự là một cơ hội tối ưu hóa nhỡ (trong một trình biên dịch mà rõ ràng ai đó đặt một số nỗ lực vào tôn trọng final vòng loại trong phôi), hoặc là tối ưu hóa không thể trong trường hợp này vì một lý do tế nhị mà tôi vẫn không nhìn thấy?

+4

Tôi đoán là tình huống này không phát sinh thường xuyên cho hầu hết các trình biên dịch phải lo lắng quá nhiều về nó. Việc tối ưu hóa được thực hiện để đáp ứng nhu cầu chung về hiệu quả trong thế giới thực. – cdhowie

+0

@cdhowie: Bạn có thể đúng; nhưng những gì cho tôi tạm dừng là ai đó rõ ràng * đã làm * đi đến những rắc rối của việc viết một tối ưu hóa Clang trong trường hợp 'from_final'.Các trường hợp 'to_final' là đối xứng (đặc biệt là trong điều khoản của codegen, nơi mà nó kéo typeinfo cho cả hai loại), nhưng chưa biết ai đó đã làm * không * thêm tối ưu hóa đối xứng. "Tối ưu hóa một cách rõ ràng được thực hiện một nửa" xuất hiện vào não của tôi hơn là "không có tối ưu hóa nào cả" (xem GCC, ICC, MSVC). – Quuxplusone

Trả lời

3

Ok, tôi đào qua Clang của source code to find this method:

/// isAlwaysNull - Return whether the result of the dynamic_cast is proven 
/// to always be null. For example: 
/// 
/// struct A { }; 
/// struct B final : A { }; 
/// struct C { }; 
/// 
/// C *f(B* b) { return dynamic_cast<C*>(b); } 
bool CXXDynamicCastExpr::isAlwaysNull() const 
{ 
    QualType SrcType = getSubExpr()->getType(); 
    QualType DestType = getType(); 

    if (const PointerType *SrcPTy = SrcType->getAs<PointerType>()) { 
    SrcType = SrcPTy->getPointeeType(); 
    DestType = DestType->castAs<PointerType>()->getPointeeType(); 
    } 

    if (DestType->isVoidType()) // always allow cast to void* 
    return false; 

    const CXXRecordDecl *SrcRD = 
    cast<CXXRecordDecl>(SrcType->castAs<RecordType>()->getDecl()); 

    //******************************************************************** 
    if (!SrcRD->hasAttr<FinalAttr>()) // here we check for Final Attribute 
    return false; // returns false for Cat 
    //******************************************************************** 

    const CXXRecordDecl *DestRD = 
    cast<CXXRecordDecl>(DestType->castAs<RecordType>()->getDecl()); 

    return !DestRD->isDerivedFrom(SrcRD); // search ancestor types 
} 

Tôi nhận được một chút mệt mỏi từ phân tích mã, nhưng nó không có vẻ với tôi rằng bạn from_final là không chỉ đơn giản là luôn luôn là null vì cuối cùng Thuộc tính, nhưng ngoài ra vì rằng Cat không nằm trong chuỗi bản ghi có nguồn gốc Dog. Cấp, nếu nó không có thuộc tính final, sau đó nó sẽ thoát sớm (như khi nó Cat là một Src), nhưng nó không nhất thiết phải luôn là rỗng.

Tôi đoán có một vài lý do cho việc này. Đầu tiên là dynamic_cast cast cả chuỗi lên và xuống. Khi bản ghi nguồn có thuộc tính cuối cùng, bạn có thể chỉ cần kiểm tra chuỗi nếu Dest là tổ tiên (vì không thể có các lớp dẫn xuất từ ​​Nguồn).

Nhưng nếu lớp học không kết thúc thì sao? Tôi nghi ngờ có thể có nhiều hơn với nó. Có lẽ nhiều thừa kế làm cho tìm kiếm lên phôi khó khăn hơn so với phôi? Không dừng mã trong trình gỡ lỗi, tất cả những gì tôi có thể làm là suy đoán.

Điều này tôi biết: isAlwaysNull là chức năng thoát sớm. Đó là một khẳng định hợp lý rằng nó đang cố gắng để chứng minh rằng kết quả là luôn luôn null. Bạn không thể chứng minh một tiêu cực (như họ nói), nhưng bạn có thể bác bỏ một tích cực.


Có lẽ bạn có thể kiểm tra lịch sử Git cho tệp và gửi email cho người đã viết chức năng đó. (hoặc ít nhất là thêm kiểm tra final).

+1

Theo tài liệu dynamic_cast, nó có thể bỏ lên, xuống và sang một bên. Tôi nghi ngờ rằng 'Final 'không chỉ ngăn cản các phôi tương lai, mà còn ngăn cản cả hai bên. Một lần nữa, tôi nghĩ rằng việc tìm kiếm các bản ghi thừa kế có thể cực kỳ tốn kém (ít nhất là đối với việc kiểm tra tầm thường). –

+0

Đẹp đào! Khi bản ghi nguồn có thuộc tính cuối cùng, bạn có thể chỉ cần kiểm tra chuỗi nếu Dest là tổ tiên "- Nhưng chú ý rằng nếu * Dest * có thuộc tính Cuối cùng, thì Dest không thể là tổ tiên của * gì cả *; không cần phải kiểm tra bất kỳ chuỗi nào trong trường hợp đó. – Quuxplusone

+0

Tôi hơi mệt một đêm qua, và tôi tiếp tục viết lại dòng đó. Trong mọi trường hợp, vâng, bạn nói đúng. Tôi không thể không nghĩ rằng lý do duy nhất mà kiểm tra không được thực hiện là 1) thời gian tìm kiếm hoặc 2) thiếu sót (như bạn đã chỉ ra). Chúc mừng! –

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