2010-07-27 16 views
14

Tôi hiện đang làm việc tích hợp gói của bên thứ ba sử dụng nhiều công cụ RTTI trên nền tảng không RTTI (Android). Về cơ bản, tôi đã thực hiện RTTI của riêng mình nhưng tôi bị kẹt trên một vấn đề.C++ - downcasting đối tượng thừa kế hình dạng kim cương mà không có RTTI/dynamic_cast

Vấn đề là rất nhiều lớp đang gặp vấn đề thừa kế kim cương vì tất cả các lớp bắt nguồn từ cùng một lớp cơ sở (đối tượng) .. và vì vậy, nếu tôi muốn downcast từ lớp cơ sở đến lớp dẫn xuất, Tôi phải sử dụng dynamic_cast - nhưng RTTI không có sẵn! Làm thế nào để chuyển đổi một đối tượng từ cha mẹ sang con khi có thừa kế ảo mà không có dynamic_cast?

Dường như rằng:

class A 
{ 
public: 
virtual char* func() { return "A"; }; 
}; 
class B : public virtual A 
{ 
public: 
//virtual char* func() { return "B"; }; 
}; 
class C : public virtual A 
{ 
public: 
//virtual char* func() { return "C"; }; 
}; 

class D : public B, public C 
{ 
public: 
//virtual char* func() { return "D"; }; 
}; 

D d; 
A* pa = static_cast<A*>(&d); 
D* pd = static_cast<D*>(pa); // can't do that! dynamic_cast does work though... 

Đó là lỗi của tôi:

lỗi C2635: không thể chuyển đổi một 'A *' một 'D *'; chuyển đổi từ một lớp cơ sở ảo được ngụ ý

lỗi C2440: 'khởi': không thể chuyển đổi từ 'test_convert :: A *' thành 'test_convert :: D *' Cast từ cơ sở để có nguồn gốc đòi hỏi dynamic_cast hoặc static_cast

Bất kỳ ý tưởng nào?

+0

heh, MS vừa nói để xóa từ khóa ảo khỏi mã và nó sẽ giải quyết được sự cố. Xem tài liệu của họ về lỗi, tôi không đùa đâu. –

+1

Ai nói với bạn rằng Android là một nền tảng không phải là rtti? Các ND5 r5 và mới hơn nên hỗ trợ RTTI (tôi tin rằng bạn cần phải bật nó với '-frtti', nhưng nó sẽ hoạt động sau đó). Ngay cả đối với các nền tảng cũ hơn, vì tất cả đều được liên kết tĩnh. –

Trả lời

12

Bạn chỉ có thể thực hiện việc truyền này với dynamic_cast; không có diễn viên nào khác sẽ làm điều này.

Nếu bạn không thể thiết kế giao diện của mình để không cần phải thực hiện loại dàn diễn viên này thì điều duy nhất bạn có thể làm là làm cho phần chức năng truyền của phân cấp lớp của bạn.

Ví dụ: (horribly hacky)

class D; 

class A 
{ 
public: 
    virtual D* GetDPtr() { return 0; } 
}; 

class B : public virtual A 
{ 
}; 

class C : public virtual A 
{ 
}; 

class D : public B, public C 
{ 
public: 
    virtual D* GetDPtr() { return this; } 
}; 
+3

Tôi không biết liệu có nên trừ đi hay không hoặc thêm cho câu trả lời này. –

+2

@Noah: Trong phòng thủ của tôi, tôi đã nói "khủng khiếp hacky"! –

+2

@Noah: phiên bản hacky trông đẹp hơn một ma trận diễn viên mà tôi đã thấy trên một dự án thời gian dài trước đây. trên hệ thống rtti-ít người tìm thấy không có gì tốt hơn để cung cấp cho mỗi lớp một id duy nhất và sử dụng id như là một chỉ số vào một ma trận hai chiều. ma trận [cast_what] [cast_to] là một id của lớp, kết quả của dynamic_cast. – Dummy00001

-2

Miễn là bạn có cách khác để đảm bảo bạn đang làm việc an toàn khi chạy, chỉ cần sử dụng reinterpret_cast.

Về cơ bản, nó giống như một dàn diễn viên kiểu C nên chỉ sử dụng nó nếu bạn có, nhưng nó sẽ cho phép đoạn mã trên biên dịch.

+1

Vâng, tôi đã thử nhưng tôi nhận được một thông báo cảnh báo (4946: reinterpret_cast được sử dụng giữa các lớp liên quan: 'class1' và 'class2'). Lưu ý rằng cảnh báo được tắt theo mặc định. Tôi tin rằng reinterpret_cast chỉ diễn giải lại con trỏ (như nó nói) nhưng nó không truyền nó và nó gây ra vấn đề khi có thừa kế ảo hoặc nhiều cha mẹ kể từ khi truyền đến cha hoặc người khác có thể sửa đổi giá trị của con trỏ. Như vậy, reinterpret_cast sẽ không hoạt động trong trường hợp của tôi, nơi bên thứ ba tôi sử dụng làm cho việc sử dụng rộng rãi các khái niệm đó (nhiều cha mẹ, kế thừa virutal, v.v.). – Adam

3

Trong hầu hết các trường hợp, mẫu khách truy cập có thể được sử dụng để tránh bị lỗi. Nó cũng có thể được sử dụng để tránh dynamic_cast.

Một số cảnh báo:

1) Phải thay đổi các lớp vi phạm.
2) Bạn có thể cần phải biết MỌI lớp học có nguồn gốc.
3) Các đối tượng phải được biết là có nguồn gốc từ ít nhất là baseclass, bạn không thể cố gắng để đúc các loại hoàn toàn không liên quan. (Điều này dường như được hoàn thành: "Tôi muốn downcast từ lớp cơ sở đến lớp dẫn xuất")

Trong ví dụ sau tôi đã sử dụng các mẫu. Đây có thể dễ dàng thoát khỏi, nhưng sẽ đòi hỏi một số nỗ lực bằng văn bản.

class A; 
class B; 
class C; 
class D; 

// completely abstract Visitor-baseclass. 
// each visit-method must return whether it handled the object 
class Visitor 
{ 
public: 
    virtual bool visit(A&) = 0; 
    virtual bool visit(B&) = 0; 
    virtual bool visit(C&) = 0; 
    virtual bool visit(D&) = 0; 
}; 

class A 
{ 
public: 
    virtual const char* func() { return "A"; }; 
    virtual void accept(Visitor& visitor) { visitor.visit(*this); } 
}; 
class B : public virtual A 
{ 
public: 
    virtual const char* func() { return "B"; }; 
    virtual void accept(Visitor& visitor) { visitor.visit(*this); } 
}; 
class C : public virtual A 
{ 
public: 
    virtual const char* func() { return "C"; }; 
    virtual void accept(Visitor& visitor) { visitor.visit(*this); } 
}; 
class D : public B, public C 
{ 
public: 
    virtual const char* func() { return "D"; }; 
    virtual void accept(Visitor& visitor) { visitor.visit(*this); } 
}; 

// implementation-superclass for visitors: 
// each visit-method is implemented and calls the visit-method with the parent-type(s) 
class InheritanceVisitor : public Visitor 
{ 
    virtual bool visit(A& a) { return false; } 
    virtual bool visit(B& b) { return visit(static_cast<A&>(b)); } 
    virtual bool visit(C& c) { return visit(static_cast<A&>(c)); } 
    virtual bool visit(D& d) { return visit(static_cast<B&>(d)) || visit(static_cast<C&>(d)); } 
}; 

template<typename T> // T must derive from A 
class DerivedCastVisitor : public InheritanceVisitor 
{ 
public: 
    DerivedCastVisitor(T*& casted) : m_casted(casted) {} 
    virtual bool visit(T& t) 
    { m_casted = &t; return true; } 
private: 
    T*& m_casted; 
}; 

// If obj is derived from type T, then obj is casted to T* and returned. 
// Else NULL is returned. 
template<typename T> 
T* derived_cast(A* obj) 
{ 
    T* t = NULL; 
    if (obj) 
    { 
    DerivedCastVisitor<T> visitor(t); 
    obj->accept(visitor); 
    } 
    return t; 
} 

int main(int argc, char** argv) 
{ 
    std::auto_ptr<A> a(new A); 
    std::auto_ptr<A> b(new B); 
    std::auto_ptr<A> c(new C); 
    std::auto_ptr<A> d(new D); 

    assert(derived_cast<A>(a.get()) != NULL); // a has exact type A 
    assert(derived_cast<B>(b.get()) != NULL); // b has exact type B 
    assert(derived_cast<A>(b.get()) != NULL); // b is derived of A 
    assert(derived_cast<C>(b.get()) == NULL); // b is not derived of C 
    assert(derived_cast<D>(d.get()) != NULL); // d has exact type D 
    assert(derived_cast<B>(d.get()) != NULL); // d is derived of B 
    assert(derived_cast<C>(d.get()) != NULL); // d is derived of C, too 
    assert(derived_cast<D>(c.get()) == NULL); // c is not derived of D 

    return 0; 
} 
0

Vấn đề với thừa kế ảo là địa chỉ lớp cơ sở không nhất thiết phải là giống với địa chỉ xuất phát. Do đó, ngay cả reinterpret_cast hoặc void* phôi sẽ không được trợ giúp.

Một cách để giải quyết điều này mà không cần sử dụng dynamic_cast là tính toán độ lệch giữa cả hai loại con trỏ (loại chính xác và loại ref) để sửa đổi địa chỉ đối tượng tương ứng trong khi truyền.

template <typename E, typename T> 
E& force_exact(const T& ref) 
{ 
    static const E* exact_obj; 
    static const T& exact_obj_ref = *exact_obj; 
    static const ptrdiff_t exact_offset = 
    (const char*)(void*)(&exact_obj_ref) 
    - (const char*)(void*)(exact_obj); 
    return *(E*)((char*)(&ref) - exact_offset); 
} 
+0

Lưu ý phụ: Tuyên bố bù đắp được thực hiện tĩnh bởi vì nó chỉ được tính một lần cho mỗi cặp T, E theo lý thuyết. Tuy nhiên trong thực tế, việc sử dụng 'static' có một chi phí thời gian chạy (được viết lại là' if (! Var_initilized) var_ = ... '). Một cách để thoát khỏi điều này là khởi tạo một lớp mẫu lưu trữ tĩnh giá trị offset cho mỗi cặp T, E nhưng đó là một câu chuyện khác ... – log0

1

mã:

template <typename E, typename T> 
E& force_exact(const T& ref) 
{ 
    static const E* exact_obj; 
    static const T& exact_obj_ref = *exact_obj; 
    static const ptrdiff_t exact_offset = ... 

không hoạt động rất tốt đối với tôi như static const E* exact_obj là zero, vì vậy tĩnh const T& exact_obj_ref = *exact_obj derefs không, quá, và do đó static const ptrdiff_t exact_offset cũng trở thành số không.

Dường như với tôi rằng lớp dẫn xuất cần phải được khởi tạo (có thể là một vấn đề đối với các lớp trừu tượng ...). Vì vậy, mã của tôi là:

template <typename D, typename B> 
D & Cast2Derived(B & b) 
{ static D d; 
    static D * pD = & d; 
    static B * pB = pD; 
    static ptrdiff_t off = (char *) pB - (char *) pD; 

    return * (D *) ((char *) & b - off); 
} 

Thử nghiệm dưới MSVC 2008, WinXP 32b.

Mọi nhận xét/giải pháp tốt hơn đều được chào đón.

LUP

+0

Đây là vấn đề nếu constructor của D phát sinh các tác dụng phụ. Có lẽ giải pháp tốt hơn là reinterpret_cast một bộ đệm char có kích thước phù hợp và phù hợp với con trỏ D. Ngoài ra, để tiếp tục với C++, hãy đặt tên cho nó là 'virtual_cast (B *)'? Tôi vẫn chưa đưa ra suy nghĩ sâu sắc, nhưng có một số hệ thống phân cấp thừa kế mà điều này sẽ thất bại không? Có lẽ một lớp đã thừa hưởng từ cùng một lớp ảo nhiều lần (ngay cả khi gián tiếp) hoặc có lẽ với sự kết hợp của thừa kế phi ảo để khởi động. Anyhow +1 –

+0

Hi Thomas, bạn có thể vui lòng mô tả tốt hơn ý tưởng của bạn về diễn giải lại bộ đệm char để D *? – Juster

4

Android không hỗ trợ RTTI. Bạn cần NDK mới nhất (ít nhất là r5, mới nhất là r6) và bạn cần biên dịch với GNU stdlibC++ thay vì mặc định. Thậm chí trước đây, đã có xây dựng lại CrystaX đã hỗ trợ ngoại lệ và rtti (chúng tôi đã phải sử dụng cho đến khi chính thức NDK r5c vì r5a và r5b có sự hỗ trợ, nhưng bị rơi trên hệ thống cũ (trước 2.3)).

PS: Ai đó thực sự cấm các nhà cung cấp nói rằng họ hỗ trợ C++ khi họ không hỗ trợ ngoại lệ và rtti, vì hầu hết thư viện chuẩn, và đó là một phần của tiêu chuẩn C++, không hoạt động mà không có. Plus không hỗ trợ chúng là ngớ ngẩn, đặc biệt là đối với các trường hợp ngoại lệ, vì mã có ngoại lệ hiệu quả hơn trường hợp không có (miễn là chúng được sử dụng đúng để báo hiệu trường hợp đặc biệt).