2010-03-12 32 views
5

Điều này được đơn giản hóa rất nhiều vì lợi ích của câu hỏi. Nói rằng tôi có một hệ thống phân cấp:Mô phỏng công văn động trong C++ dựa trên các tham số mẫu

struct Base { 
    virtual int precision() const = 0; 
}; 

template<int Precision> 
struct Derived : public Base { 

    typedef Traits<Precision>::Type Type; 

    Derived(Type data) : value(data) {} 
    virtual int precision() const { return Precision; } 

    Type value; 

}; 

Tôi muốn có một phi mẫu chức năng với chữ ký:

Base* function(const Base& a, const Base& b); 

Trường hợp loại hình cụ thể về kết quả của hàm là loại giống như bất cứ của ab có số lớn hơn Precision; một cái gì đó giống như giả sau đây:

Base* function(const Base& a, const Base& b) { 

    if (a.precision() > b.precision()) 

     return new A(((A&)a).value + A(b.value).value); 

    else if (a.precision() < b.precision()) 

     return new B(B(((A&)a).value).value + ((B&)b).value); 

    else 

     return new A(((A&)a).value + ((A&)b).value); 

} 

đâu AB là các loại cụ thể của ab, tương ứng. Tôi muốn function hoạt động độc lập với bao nhiêu phiên bản của Derived. Tôi muốn tránh một bảng lớn của sự so sánh typeid(), mặc dù RTTI là tốt trong câu trả lời. Bất kỳ ý tưởng?

+2

Tôi nghĩ bạn nên đề cập rằng bạn không biết loại lớp hoàn chỉnh. Bạn chỉ biết 'Base &'. Một số câu trả lời, bao gồm cả câu trả lời của riêng tôi, đã giả sử bạn biết loại chính xác 'Có nguồn gốc '. –

+0

Lưu ý từ một nhận xét về câu trả lời: Một yêu cầu bổ sung là hàm không thể là mẫu; nó phải có chữ ký Base (Base & Base). –

+0

Kết hợp các hạn chế rõ ràng hơn vào câu hỏi. –

Trả lời

3

Bạn không thể có hàm() ủy quyền mã templated trực tiếp mà không chọn giữa danh sách khổng lồ tất cả các loại có thể, vì mẫu được mở rộng tại thời gian biên dịch, và tại hàm biên dịch() không biết các loại thực sự sẽ được gọi với. Bạn cần phải có các instantiations được biên dịch của mã templated cho mọi phiên bản của hàm operation được yêu cầu của bạn, nó sẽ được yêu cầu, có khả năng là một tập hợp vô hạn.

Theo logic đó, nơi duy nhất biết tất cả các mẫu có thể được yêu cầu là chính lớp học Derived.Do đó, lớp Derived của bạn nên bao gồm một thành viên:

Derived<Precision> *operation(Base& arg2) { 
    Derived<Precision> *ptr = new Derived<Precision>; 
    // ... 
    return ptr; 
} 

Sau đó, bạn có thể xác định function như vậy, và thực hiện điều phối gián tiếp:

Base* function(const Base& a, const Base& b) { 
    if (a.precision() > b.precision()) 
    return a.operation(b); 
    else 
    return b.operation(a); 
} 

Lưu ý rằng đây là phiên bản đơn giản; nếu hoạt động của bạn không đối xứng trong các đối số của nó, bạn sẽ cần xác định hai phiên bản của hàm thành viên - một với this thay cho đối số đầu tiên và một với nó ở vị trí thứ hai.

Ngoài ra, điều này đã bỏ qua thực tế là bạn cần một số cách để a.operation nhận dạng thích hợp b.value mà không biết loại bắt nguồn là b. Bạn sẽ phải tự giải quyết vấn đề đó - lưu ý rằng nó (bằng cùng một logic như trước đó) không thể giải quyết điều này bằng cách tạo khuôn mẫu trên loại b, bởi vì bạn đang gửi đi vào thời gian chạy. Giải pháp phụ thuộc vào chính xác loại bạn đã có, và liệu có cách nào đó cho một loại có độ chính xác cao hơn để kéo giá trị ra khỏi đối tượng chính xác bằng hoặc thấp hơn đối tượng Derived mà không biết chính xác loại đối tượng đó. Điều này có thể không thực hiện được, trong trường hợp này, bạn có danh sách dài khớp với ID loại.

Mặc dù vậy, bạn không phải thực hiện điều đó trong báo cáo chuyển đổi. Bạn có thể cung cấp cho mỗi Derived nhập một tập hợp các hàm thành viên để truyền lên tới một hàm có độ chính xác cao hơn. Ví dụ:

template<int i> 
upCast<Derived<i> >() { 
    return /* upcasted value of this */ 
} 

Sau đó, chức năng operator thành viên của bạn có thể hoạt động trên b.upcast<typeof(this)>, và sẽ không cần phải làm một cách rõ ràng đúc để có được một giá trị của các loại nó cần. Bạn cũng có thể phải thực hiện một cách rõ ràng một số chức năng này để chúng được biên dịch; Tôi đã không làm đủ công việc với RTTI để nói chắc chắn.

Về cơ bản, tuy nhiên, vấn đề là nếu bạn có N điểm có thể, bạn đã có N N kết hợp có thể, và mỗi thực tế trong số này sẽ cần phải có mã được biên dịch riêng. Nếu bạn không thể sử dụng các mẫu trong định nghĩa của mình là function, thì bạn phải biên dịch phiên bản của tất cả N N trong những khả năng này, và bằng cách nào đó bạn phải báo cho trình biên dịch tạo tất cả, và bằng cách nào đó bạn phải chọn đúng một để gửi đến thời gian chạy. Bí quyết sử dụng chức năng thành viên sẽ lấy ra một trong những yếu tố đó của N, nhưng cái còn lại vẫn còn, và không có cách nào làm cho nó hoàn toàn giống nhau.

+0

Điều này là đủ tốt đối với tôi.Yếu tố thứ hai được loại bỏ bởi thực tế là 'Base' định nghĩa một cơ sở để chuyển đổi trong thời gian chạy thành một kiểu tùy ý. –

+0

Ồ, tốt! Tôi chỉ đang chỉnh sửa để gợi ý rằng một cơ sở như vậy sẽ hữu ích cho việc loại bỏ yếu tố thứ hai. –

1

Trước tiên, bạn muốn làm cho precision thành viên của mình là giá trị static const int, không phải là hàm, để bạn có thể hoạt động trên đó tại thời gian biên dịch. Trong Derived, nó sẽ là:

static const int precision = Precision; 

Sau đó, bạn cần một số cấu trúc helper để xác định cơ sở/lớp Derived nhất-chính xác. Trước tiên, bạn cần một struct helper-helper generic để chọn một trong hai loại tùy thuộc vào một boolean:

template<typename T1, typename T2, bool use_first> 
struct pickType { 
    typedef T2 type; 
}; 

template<typename T1, typename T2> 
struct pickType<T1, T2, true> { 
    typedef T1 type; 
}; 

Sau đó, pickType<T1, T2, use_first>::type sẽ giải quyết để T1 nếu use_firsttrue, và nếu không để T2. Vì vậy, sau đó chúng tôi sử dụng này để chọn các loại chính xác nhất:

template<typename T1, typename T2> 
struct mostPrecise{ 
    typedef pickType<T1, T2, (T1::precision > T2::precision)>::type type; 
}; 

Bây giờ, mostPrecise<T1, T2>::type sẽ cung cấp cho bạn bất cứ của hai loại có một giá trị lớn precision. Vì vậy, bạn có thể xác định chức năng của mình là:

template<typename T1, typename T2> 
mostPrecise<T1, T2>::type function(const T1& a, const T2& b) { 
    // ... 
} 

Và ở đó bạn có nó.

+0

Điều này rất thú vị và hữu ích cho một dự án khác của tôi, nhưng tôi đặc biệt cần quyết định được thực hiện trong thời gian chạy. –

+0

Cảm ơn. Tôi sẽ để cái này lên cho giá trị lịch sử hơn là xóa nó, sau đó, nhưng xem câu trả lời khác của tôi cho ý kiến ​​về làm điều này trong thời gian chạy. –

4

Những gì bạn đang yêu cầu được gọi là multiple dispatch, còn gọi là multimethods. Nó không phải là một tính năng của ngôn ngữ C++.

Có giải pháp cho các trường hợp đặc biệt, nhưng bạn không thể tránh tự mình thực hiện một số thao tác.

Một mẫu phổ biến cho nhiều công văn được gọi là "redispatch", còn gọi là "gửi đi trì hoãn đệ quy". Về cơ bản, một phương thức ảo giải quyết một kiểu tham số sau đó gọi một phương thức ảo khác, cho đến khi tất cả các tham số được giải quyết. Chức năng của bên ngoài (nếu có) chỉ gọi phương thức ảo đầu tiên.

Giả sử có n lớp dẫn xuất, sẽ có một phương pháp để giải quyết tham số đầu tiên, n để giải quyết tham số thứ hai, n * n để giải quyết vấn đề thứ ba và cứ thế - tệ nhất. Đó là một số tiền hợp lý của công việc thủ công, và sử dụng các khối điều kiện dựa trên typeid có thể dễ dàng hơn cho sự phát triển ban đầu, nhưng nó mạnh mẽ hơn để bảo trì sử dụng redispatch.

class Base; 
class Derived1; 
class Derived2; 

class Base 
{ 
    public: 
    virtual void Handle (Base* p2); 

    virtual void Handle (Derived1* p1); 
    virtual void Handle (Derived2* p1); 
}; 

class Derived1 : public Base 
{ 
    public: 
    void Handle (Base* p2); 

    void Handle (Derived1* p1); 
    void Handle (Derived2* p1); 
}; 

void Derived1::Handle (Base* p2) 
{ 
    p2->Handle (this); 
} 

void Derived1::Handle (Derived1* p1) 
{ 
    // p1 is Derived1*, this (p2) is Derived1* 
} 

void Derived1::Handle (Derived2* p1) 
{ 
    // p1 is Derived2*, this (p2) is Derived1* 
} 

// etc 

Thực hiện điều này bằng cách sử dụng mẫu cho các lớp học có nguồn gốc sẽ rất khó, và các mẫu Lập trình meta để xử lý nó có lẽ sẽ không thể đọc được, unmaintainable và rất mong manh. Triển khai công văn bằng cách sử dụng các phương thức không phải mẫu, sau đó sử dụng một mẫu mixin (một lớp mẫu lấy lớp cơ sở của nó làm tham số mẫu) để mở rộng với các tính năng bổ sung có thể không quá tệ.

visitor design pattern liên quan chặt chẽ đến (cơ bản được triển khai bằng cách sử dụng) redispatch IIRC.

Cách tiếp cận khác là sử dụng ngôn ngữ được thiết kế để xử lý sự cố và có một vài tùy chọn hoạt động tốt với C++. Một là sử dụng treecc - một ngôn ngữ dành riêng cho miền để xử lý các nút AST và các phép toán nhiều công văn, như lex và yacc, tạo ra "mã nguồn" làm đầu ra.

Tất cả việc cần làm để xử lý các quyết định gửi là tạo các câu lệnh chuyển đổi dựa trên ID nút AST - có thể dễ dàng là ID lớp giá trị được nhập động, IYSWIM. Tuy nhiên, đây là những câu lệnh chuyển đổi mà bạn không phải viết hoặc duy trì, đó là một sự khác biệt chính. Vấn đề lớn nhất mà tôi có là các nút AST bị xử lý hủy hoại của chúng giả mạo, có nghĩa là các destructors cho dữ liệu thành viên không được gọi trừ khi bạn thực hiện một nỗ lực đặc biệt - tức là nó hoạt động tốt nhất với các loại POD cho các trường.

Một tùy chọn khác là sử dụng bộ xử lý tiền xử lý ngôn ngữ hỗ trợ đa phương thức. Đã có một vài trong số này, một phần vì Stroustrup đã có những ý tưởng phát triển khá tốt để hỗ trợ multimethods tại một thời điểm. CMM là một. Doublecpp là loại khác. Tuy nhiên, khác là Frost Project. Tôi tin rằng CMM là gần nhất với những gì Stroustrup mô tả, nhưng tôi đã không kiểm tra.

Cuối cùng, mặc dù, nhiều công văn chỉ là một cách để đưa ra quyết định thời gian chạy và có nhiều cách để xử lý cùng một quyết định. DSL chuyên gia mang lại một số tiền hợp lý của rắc rối, vì vậy bạn thường chỉ làm điều đó nếu bạn cần rất nhiều công văn nhiều. Redispatch và mô hình khách truy cập là bảo trì WRT mạnh mẽ, nhưng với chi phí phức tạp và lộn xộn. Các câu lệnh điều kiện đơn giản có thể là một lựa chọn tốt hơn cho các trường hợp đơn giản, mặc dù hãy cẩn thận rằng việc phát hiện khả năng của một trường hợp không được giải quyết lúc biên dịch là rất khó nếu không phải là không thể.

Như thường lệ, không có cách nào phù hợp để làm điều đó, ít nhất là trong C++.

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