2010-02-04 29 views
28

Tôi có cấu trúc lớp này.C++ Mẫu đa hình

class Interface{ 
... 
} 

class Foo : public Interface{ 
... 
} 

template <class T> 
class Container{ 
... 
} 

Và tôi có hàm tạo này của một số thanh hạng khác.

Bar(const Container<Interface> & bar){ 
... 
} 

Khi tôi gọi hàm tạo theo cách này, tôi gặp lỗi "không có hàm phù hợp".

Container<Foo> container(); 

Bar * temp = new Bar(container); 

Điều gì là sai? Không phải là mẫu đa hình?

+0

Mẫu không đa hình. Các mẫu được ràng buộc tại thời gian biên dịch, không giống như các đối tượng đa hình bị ràng buộc tại thời gian chạy. –

+0

Các câu hỏi liên quan: http://stackoverflow.com/questions/1289167/template-polymorphism-not-working http://stackoverflow.com/questions/639248/c-covariant-templates –

Trả lời

38

Tôi nghĩ rằng thuật ngữ chính xác cho những gì bạn cần là "hiệp phương sai khuôn mẫu", có nghĩa là nếu B kế thừa từ A, thì bằng cách nào đó, T<B> được kế thừa từ T<A>. Đây không phải là trường hợp trong C++, cũng không phải là với Java và C# generics *.

Có một lý do chính đáng để tránh hiệp phương sai khuôn mẫu: điều này sẽ loại bỏ tất cả các loại an toàn trong lớp mẫu. Hãy để tôi giải thích với các ví dụ sau:

//Assume the following class hierarchy 
class Fruit {...}; 

class Apple : public Fruit {...}; 

class Orange : public Fruit {...}; 

//Now I will use these types to instantiate a class template, namely std::vector 
int main() 
{ 
    std::vector<Apple> apple_vec; 
    apple_vec.push_back(Apple()); //no problem here 

    //If templates were covariant, the following would be legal 
    std::vector<Fruit> & fruit_vec = apple_vec; 

    //push_back would expect a Fruit, so I could pass it an Orange 
    fruit_vec.push_back(Orange()); 

    //Oh no! I just added an orange in my apple basket! 
} 

Do đó, bạn nên cân nhắc T<A>T<B> loại như hoàn toàn không liên quan, không phụ thuộc vào mối quan hệ giữa A và B.

Vậy làm thế nào bạn có thể giải quyết vấn đề bạn' lại phải đối mặt? Trong Java và C#, bạn có thể sử dụng tương ứng ký tự đại diện bị chặnchế:

//Java code 
Bar(Container<? extends Interface) {...} 

//C# code 
Bar<T>(Container<T> container) where T : Interface {...} 

Tiếp theo C++ Standard (gọi là C++ 1x (trước đây là C++ 0x)) ban đầu chứa một thậm chí mạnh mẽ hơn cơ chế có tên Concepts, điều đó sẽ cho phép nhà phát triển thực thi các yêu cầu về cú pháp và/hoặc ngữ nghĩa đối với các tham số mẫu, nhưng tiếc là đã bị trì hoãn đến một ngày sau đó. Tuy nhiên, Boost có số Concept Check library mà bạn có thể quan tâm.

Tuy nhiên, khái niệm có thể hơi quá mức đối với sự cố bạn gặp phải, việc sử dụng xác nhận tĩnh đơn giản như được đề xuất bởi @gf có lẽ là giải pháp tốt nhất.

* Cập nhật: Vì .Net Framework 4, có thể đánh dấu các thông số chung là covariant or contravariant.

5

No. Hãy tưởng tượng tham số vùng chứa được "mã hóa cứng" vào lớp mà nó xác định (và đó thực sự là cách nó hoạt động). Do đó loại vùng chứa là Container_Foo, không tương thích với Container_Interface.

gì bạn có thể thử tuy nhiên là thế này:

template<class T> 
Bar(const Container<T> & bar){ 
... 
} 

Tuy nhiên, bạn lỏng lẻo loại trực tiếp kiểm tra theo cách đó.

Trên thực tế cách STL (có thể là hiệu quả và chung chung hơn) sẽ làm

template<class InputIterator> 
Bar(InputIterator begin, InputIterator end){ 
... 
} 

... nhưng tôi giả sử bạn không có vòng lặp thực hiện trong container.

+0

Điều đó rất buồn. Cảm ơn về lời khuyên. Tôi không thích giải pháp đó nhưng tôi e là nó chỉ còn lại. –

+0

Bạn giả định đúng. Tôi không cần chúng theo cách đặc biệt đó. Thành thật mà nói không biết làm thế nào để thực hiện chúng và không có thời gian để tìm hiểu nó ngay bây giờ. –

11

Có hai vấn đề ở đây: các công trình mặc định có dạng MyClass c;; với dấu ngoặc đơn, nó trông giống như một khai báo hàm cho trình biên dịch.

Vấn đề khác là Container<Interface> chỉ đơn giản là một loại khác nhau sau đó Container<Foo> - bạn có thể làm như sau thay vì để thực sự có được đa hình:

Bar::Bar(const Container<Interface*>&) {} 

Container<Interface*> container; 
container.push_back(new Foo); 
Bar* temp = new Bar(container); 

Hoặc tất nhiên bạn có thể làm Bar hoặc constructor của nó một mẫu như Kornel đã cho thấy.

Nếu bạn thực sự muốn có một số kiểu an thời gian biên dịch đa hình, bạn có thể sử dụng Boost.TypeTraitsis_base_of hoặc một số tương đương:

template<class T> 
Bar::Bar(const Container<T>& c) { 
    BOOST_STATIC_ASSERT((boost::is_base_of<Interface, T>::value)); 
    // ... will give a compile time error if T doesn't 
    // inherit from Interface 
} 
+0

Cảm ơn. Tôi sẽ thử nó. –

+0

+1: hãy nghĩ về nó, đây sẽ là một giải pháp đẹp hơn. –

+0

Đó là thực sự tốt đẹp và chính xác những gì tôi cần. Tôi sẽ không phải thay đổi nhiều mã đã được triển khai. Cảm ơn một lần nữa. –

-1

container là một container của Foo đối tượng không phải là một thùng chứa các đối tượng giao diện

Và nó không thể được đa hình hoặc, con trỏ đến những thứ có thể được, nhưng không phải là đối tượng themselvs. Làm thế nào lớn các khe trong container sẽ phải cho container nếu bạn có thể đặt bất cứ điều gì có nguồn gốc từ giao diện trong nó

bạn cần

container<Interface*> 

hoặc tốt hơn

container<shared_ptr<Interface> > 
+1

@ pm100, shared_ptr ? Bạn đã bao giờ sử dụng shared_ptr chưa ?? –

+1

@kornel có thể là lỗi đánh máy ... – msi

+0

@msiemeri - đúng, nhưng lưu ý rằng phần còn lại của câu trả lời giả định rằng một giá trị cửa hàng lưu trữ, trong khi nó thực sự có thể lưu trữ Giao diện * –

2

Có thể tạo cây thừa kế cho vùng chứa, phản ánh cây kế thừa của dữ liệu. Nếu bạn có các dữ liệu sau:

class Interface { 
public: 
    virtual ~Interface() 
     {} 
    virtual void print() = 0; 
}; 

class Number : public Interface { 
public: 
    Number(int value) : x(value) 
     {} 
    int get() const 
     { return x; } 
    void print() 
     { std::printf("%d\n", get()); }; 
private: 
    int x; 
}; 

class String : public Interface { 
public: 
    String(const std::string & value) : x(value) 
     {} 
    const std::string &get() const 
     { return x; } 
    void print() 
     { std::printf("%s\n", get().c_str()); } 
private: 
    std::string x; 
}; 

Bạn cũng có thể có những container sau đây:

class GenericContainer { 
public: 
    GenericContainer() 
     {} 
    ~GenericContainer() 
     { v.clear(); } 

    virtual void add(Interface &obj) 
     { v.push_back(&obj); } 
    Interface &get(unsigned int i) 
     { return *v[ i ]; } 
    unsigned int size() const 
     { return v.size(); } 
private: 
    std::vector<Interface *> v; 
}; 

class NumericContainer : public GenericContainer { 
public: 
    virtual void add(Number &obj) 
     { GenericContainer::add(obj); } 
    Number &get(unsigned int i) 
     { return (Number &) GenericContainer::get(i); } 
}; 

class TextContainer : public GenericContainer { 
public: 
    virtual void add(String &obj) 
     { GenericContainer::add(obj); } 
    String &get(unsigned int i) 
     { return (String &) GenericContainer::get(i); } 
}; 

Đây không phải là mã hiệu suất tốt nhất; nó chỉ là để đưa ra một ý tưởng. Vấn đề duy nhất với phương pháp này là mỗi khi bạn thêm một lớp dữ liệu mới, bạn cũng phải tạo một Vùng chứa mới. Ngoài ra, bạn có đa hình "làm việc một lần nữa". Bạn có thể cụ thể hoặc chung:

void print(GenericContainer & x) 
{ 
    for(unsigned int i = 0; i < x.size(); ++i) { 
     x.get(i).print(); 
    } 
} 

void printNumbers(NumericContainer & x) 
{ 
    for(unsigned int i = 0; i < x.size(); ++i) { 
     printf("Number: "); 
     x.get(i).print(); 
    } 
} 

int main() 
{ 
    TextContainer strContainer; 
    NumericContainer numContainer; 
    Number n(345); 
    String s("Hello"); 

    numContainer.add(n); 
    strContainer.add(s); 

    print(strContainer); 
    print(numContainer); 
    printNumbers(numContainer); 
} 
2

Tôi đề xuất giải pháp sau, sử dụng chức năng mẫu. Mặc dù ví dụ này sử dụng QList Qt, nhưng không có gì ngăn cản giải pháp được chuyển thẳng sang bất kỳ vùng chứa nào khác.

template <class D, class B> // D (Derived) inherits from B (Base) 
QList<B> toBaseList(QList<D> derivedList) 
{ 
    QList<B> baseList; 
    for (int i = 0; i < derivedList.size(); ++i) { 
     baseList.append(derivedList[i]); 
    } 
    return baseList; 
} 

Ưu điểm:

  • chung
  • kiểu an
  • khá hiệu quả nếu các mục là con trỏ hoặc một số khác với giá rẻ copy-constructible yếu tố (như các lớp học Qt ngầm chia sẻ)

Nhược điểm:

  • yêu cầu tạo vùng chứa mới, trái ngược với việc cho phép tái sử dụng vùng chứa ban đầu
  • ngụ ý một số bộ nhớ và bộ xử lý cần tạo và điền vào vùng chứa mới, phụ thuộc nhiều vào chi phí của bản sao -constructor