2011-10-06 24 views
8

Tôi đang tìm một nhà máy trừu tượng cho các mẫu lớp, nơi các lớp đăng ký tự động tại thời gian khởi tạo tĩnh. Đối với các lớp thông thường (không có templated), giải pháp là đủ đơn giản bằng cách sử dụng các thành viên tĩnh. Dưới đây là một ví dụ về một (chứ không phải đơn giản) giải pháp mà làm việc tốt:Đăng ký nhà máy tự động biên dịch các mẫu lớp trong C++

#include <cassert> 
#include <iostream> 

class Base { 
public: 
    virtual size_t id() const = 0; 
    virtual const char* name() const = 0; 
    virtual ~Base() {} 
}; 

typedef Base* (*CreateFunc)(void); 

class SimpleFactory { 
private: 
    static const size_t NELEM = 2; 
    static size_t id_; 
    static CreateFunc creators_[NELEM]; 

public: 
    static size_t registerFunc(CreateFunc creator) { 
    assert(id_ < NELEM); 
    assert(creator); 
    creators_[id_] = creator; 
    return id_++; 
    } 

    static Base* create(size_t id) { assert(id < NELEM); return (creators_[id])(); } 
}; 

size_t SimpleFactory::id_ = 0; 
CreateFunc SimpleFactory::creators_[NELEM]; 


class D1 : public Base { 
private: 
    static Base* create() { return new D1; } 
    static const size_t id_; 

public: 
    size_t id() const { return id_; } 
    const char* name() const { return "D1"; } 
}; 

const size_t D1::id_ = SimpleFactory::registerFunc(&create); 

class D2 : public Base { 
private: 
    static Base* create() { return new D2; } 
    static const size_t id_; 

public: 
    size_t id() const { return id_; } 
    const char* name() const { return "D2"; } 
}; 

const size_t D2::id_ = SimpleFactory::registerFunc(&create); 

int main() { 
    Base* b1 = SimpleFactory::create(0); 
    Base* b2 = SimpleFactory::create(1); 
    std::cout << "b1 name: " << b1->name() << "\tid: " << b1->id() << "\n"; 
    std::cout << "b2 name: " << b2->name() << "\tid: " << b2->id() << "\n"; 
    delete b1; 
    delete b2; 
    return 0; 
} 

Câu hỏi tôi có là làm thế nào để làm cho nó hoạt khi những thứ tôi muốn đăng ký/tạo được nhiều hơn như:

template <typename T> class Base... 
template <typename T> class D1 : public Base<T> ... 

ý tưởng tốt nhất mà tôi có thể nghĩ đến là template nhà máy là tốt, cái gì đó như:

template <typename T> 
class SimpleFactory { 
private: 
    static const size_t NELEM = 2; 
    static size_t id_; 
    typedef Base<T>* Creator; 
    static Creator creators_[NELEM]; 
...(the rest remains largely the same) 

Nhưng tôi tự hỏi nếu có một cách tốt hơn, hoặc nếu ai đó đã thực hiện một mô hình như vậy trước đây.

EDIT: xem lại vấn đề này một vài năm sau (và với các mẫu variadic), tôi có thể tiến gần hơn đến những gì tôi muốn bằng cách đơn giản là "đăng ký" chức năng, hay đúng hơn là các tham số mẫu. Nó sẽ trông giống như thế này:

#include <cassert> 

struct Base {}; 

struct A : public Base { 
    A() { std::cout << "A" << std::endl; } 
}; 

struct B : public Base { 
    B() { std::cout << "B" << std::endl; } 
}; 

struct C : public Base { 
    C() { std::cout << "C" << std::endl; } 
}; 

struct D : public Base { 
    D() { std::cout << "D" << std::endl; } 
}; 


namespace { 
    template <class Head> 
    std::unique_ptr<Base> 
    createAux(unsigned id) 
    { 
    assert(id == 0); 
    return std::make_unique<Head>(); 
    } 

    template <class Head, class Second, class... Tail> 
    std::unique_ptr<Base> 
    createAux(unsigned id) 
    { 
    if (id == 0) { 
     return std::make_unique<Head>(); 
    } else { 
     return createAux<Second, Tail...>(id - 1); 
    } 
    } 
} 

template <class... Types> 
class LetterFactory { 
public: 
    std::unique_ptr<Base> 
    create(unsigned id) const 
    { 
    static_assert(sizeof...(Types) > 0, "Need at least one type for factory"); 
    assert(id < sizeof...(Types)); 
    return createAux<Types...>(id); 
    } 
}; 

int main() { 
    LetterFactory<A, B, C, D> fac; 
    fac.create(3); 
    return 0; 
} 

Bây giờ, đây chỉ là một nguyên mẫu đơn giản, vì vậy đừng bao giờ tạo ra sự phức tạp tuyến tính(). Tuy nhiên, thiếu sót chính của thiết kế này là nó không cho phép bất kỳ tham số khởi tạo nào. Lý tưởng nhất, tôi có thể đăng ký không chỉ các lớp mà nhà máy cần tạo, mà còn là các kiểu mà mỗi lớp đưa vào trong hàm khởi tạo của nó, và để tạo() đưa chúng một cách hiệu quả. Có ai từng làm một cái gì đó như thế này trước đây không?

+4

Trái ngược với niềm tin phổ biến, các chức năng của bạn không * tự đăng ký tại * thời gian biên dịch *, mà là bắt đầu chương trình, trước khi 'main()' được gọi. Điều này một phần vì 'registerFunc' không được khai báo là' constexpr', nhưng cũng bởi vì nó không thực sự có thể theo cách bạn đã viết nó. –

+1

Nếu bạn muốn trở nên lạ mắt, bạn có thể tạo một đăng ký người tạo duy nhất, toàn cầu về loại 'std :: map '. Để truy xuất templated theo kiểu 'T' bạn muốn' m [typeid (T)] 'thành' Base (* create)() '. –

+0

Tất nhiên bạn đang đúng về thời gian biên dịch/thời gian init. Xin lỗi vì sự nhầm lẫn. Đối với ý tưởng của bạn cho loại tẩy xoá - nó không phải là xấu. Nó sẽ được khá mặc dù nếu tôi có thể đã ẩn những chi tiết trong một tập tin định nghĩa, nhưng mẫu T ngăn chặn điều đó. – Eitan

Trả lời

0

Làm những việc đơn giản hơn sẽ phá vỡ ít hơn và làm cho ý định của bạn hiển nhiên.

int main() { 
    RegisterConcreteTypeFoo(); 
    RegisterConcreteTypeBar(); 
    // do stuff... 
    CleanupFactories(); 
    return 0; 
} 

Khi các chức năng init đó thực sự được gọi (không phải lúc biên dịch) và không thành công, bạn sẽ không nhận được mọi thứ dễ dàng hơn. Giống như một dấu vết ngăn xếp.

Với trường hợp này, bạn cũng cho rằng bạn sẽ không muốn khởi tạo chúng khác nhau. Nó là quá phức tạp để kiểm tra đơn vị "tự động đăng ký" bất cứ điều gì, ví dụ.

Ít phép thuật = dễ dàng hơn, bảo trì rẻ hơn.

Nếu điều đó không đủ, cũng có sự cố kỹ thuật. Các trình biên dịch như loại bỏ các biểu tượng không sử dụng khỏi các thư viện. Có thể có một trình biên dịch cụ thể shenanigan để có được xung quanh nó, tôi không chắc chắn. Hy vọng rằng nó thực hiện nó một cách nhất quán, thay vì ngẫu nhiên không có lý do rõ ràng ở giữa một chu kỳ phát triển.

+0

Tôi đồng ý với nguyên tắc chung là ít phép thuật dễ dàng hơn. Tôi đánh giá cao rằng bạn đang cố gắng chỉ đạo tôi làm "Điều đúng" chung. Hãy tin tưởng rằng tôi cũng được thông báo về giải pháp đơn giản, và đã cố ý và cụ thể yêu cầu cho trường hợp góc này. Việc sử dụng thực tế của tôi phức tạp hơn so với ví dụ đơn giản mà tôi đã trình bày, bởi vì tôi không muốn nhầm lẫn câu hỏi với các chi tiết không liên quan đến thử thách kỹ thuật cụ thể. – Eitan

+0

Câu chuyện ngắn về lý do tôi nhấn mạnh vào đăng ký tự động là khả năng mở rộng và khả năng mở rộng: Tôi dự định có nhiều đối tượng trong cấu trúc phân cấp và tôi không muốn quên đăng ký bất kỳ đối tượng nào theo cách thủ công. Hơn nữa, thư viện được thiết kế cho bất cứ ai để thêm các đối tượng có nguồn gốc riêng của họ mà không cần phải chạm vào một điểm đăng ký trung tâm. Việc tranh cãi điều này sẽ đòi hỏi nhiều ngữ cảnh hơn - nhưng chúng ta có thể tập trung vào câu hỏi ban đầu của mình thay vào đó không? – Eitan

+2

@Tom Điều này thực sự sẽ rất khó để duy trì trong các dự án lớn hơn. Mỗi khi bạn thêm hoặc xóa một lớp, bạn cũng phải thêm một hàm gọi khác trong một tệp hoàn toàn khác và thậm chí có thể tạo một hàm đăng ký khác cho từng loại. –

3

Tôi đã đăng câu trả lời cho một vấn đề tương tự tại GameDev, nhưng giải pháp không phải là thời gian biên dịch. Bạn có thể xem tại đây:
>https://gamedev.stackexchange.com/questions/17746/entity-component-systems-in-c-how-do-i-discover-types-and-construct-components/17759#17759

Tôi không nghĩ rằng đó còn là cách để làm cho thời gian biên dịch này. "Id" của bạn bên trong lớp cơ sở thực sự chỉ là một dạng RTTI được đơn giản hóa, theo định nghĩa thời gian chạy. Có thể nếu bạn tạo id cho một đối số mẫu ... nhưng điều đó sẽ làm cho một số thứ khác phức tạp hơn nhiều.

+0

Bạn nói đúng về thời gian biên dịch, tất nhiên. Tôi đã chỉnh sửa câu hỏi gốc để xóa bỏ sự nhầm lẫn. – Eitan

+0

Sửa lỗi nếu tôi sai, nhưng bài đăng của bạn chỉ đơn thuần là một cách rõ ràng hơn để triển khai ví dụ ban đầu mà tôi đã đăng ở trên. Nó dường như không giải quyết được vấn đề nhà máy lớp mẫu. – Eitan

+0

Hmm, bạn nói đúng. Nếu bạn muốn chỉ giữ một nhà máy, thì tùy chọn duy nhất là có một lớp cơ sở không phải mẫu. Có lẽ bạn nên đăng một bản phác thảo về giao diện lý tưởng sẽ trông như thế nào đối với bạn. :) –

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