2009-03-12 7 views
16

Tôi cảm thấy như thế này đã được yêu cầu trước đó, nhưng tôi không thể tìm thấy nó trên SO, cũng không phải tôi có thể tìm thấy bất cứ điều gì hữu ích trên Google. Có lẽ "covariant" không phải là từ tôi đang tìm kiếm, nhưng khái niệm này rất giống với các kiểu trả về biến đổi trên các hàm, vì vậy tôi nghĩ nó có lẽ đúng. Dưới đây là những gì tôi muốn làm và nó mang lại cho tôi một lỗi trình biên dịch:C++ mẫu covariant

class Base; 
class Derived : public Base; 

SmartPtr<Derived> d = new Derived; 
SmartPtr<Base> b = d; // compiler error 

Giả sử các lớp đó hoàn toàn có thịt ... Tôi nghĩ bạn sẽ có ý tưởng. Nó không thể chuyển đổi một số SmartPtr<Derived> thành một số SmartPtr<Base> vì một số lý do không rõ ràng. Tôi nhớ rằng điều này là bình thường trong C++ và nhiều ngôn ngữ khác, mặc dù tại thời điểm này tôi không thể nhớ tại sao.

Câu hỏi gốc của tôi là: cách tốt nhất để thực hiện thao tác gán này là gì? Hiện tại, tôi đang kéo con trỏ ra khỏi số SmartPtr, đưa nó lên loại cơ sở, sau đó gói nó vào một loại mới SmartPtr của loại thích hợp (lưu ý rằng đây không phải là tài nguyên bị rò rỉ vì lớp học SmartPtr được trồng tại nhà của chúng tôi sử dụng tham chiếu xâm nhập đếm). Đó là dài và lộn xộn, đặc biệt là khi tôi sau đó cần phải bọc SmartPtr trong một đối tượng khác ... bất kỳ phím tắt?

Trả lời

11

Cả trình tạo bản sao và toán tử gán đều có thể lấy SmartPtr của một loại khác và cố gắng sao chép con trỏ từ cái này sang cái khác. Nếu các loại không tương thích, trình biên dịch sẽ phàn nàn và nếu chúng tương thích, bạn đã giải quyết được vấn đề của mình. Một cái gì đó như thế này:

template<class Type> class SmartPtr 
{ 
    .... 
    template<class OtherType> SmartPtr(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> copy constructor 

    template<class OtherType> SmartPtr<Type> &operator=(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> assignment operator 
}; 
+0

ẩn các nhận xét @MSN: Tôi đã học bằng cách dùng thử và lỗi rằng điều này là không đủ để hoàn thành trình tạo bản sao "bình thường" và toán tử gán. Vì vậy, bạn phải triển khai cả hai: SmartPtr (const SmartPtr &) VÀ mẫu SmartPtr (const SmartPtr &) (tương tự cho op =) – mmmmmmmm

+0

Vâng vâng, đó là ý của tôi :) – MSN

+0

Từ C++ 11 trở đi bạn cũng thêm move-constructor và move-assignment (những người có '&&'). –

3

Phụ thuộc vào lớp học SmartPtr. Nếu nó có một hàm tạo bản sao (hoặc trong trường hợp của bạn, toán tử gán) mất SmartPtr<T>, trong đó T là kiểu được xây dựng, thì nó sẽ không hoạt động, vì SmartPtr<T1> không liên quan đến SmartPtr<T2> ngay cả khi T1 và T2 là liên quan đến thừa kế.

Tuy nhiên, nếu SmartPtr có templatized nhà xây dựng/nhà điều hành bản sao gán, với thông số mẫu TOther, chấp nhận SmartPtr<TOther>, thì nó sẽ hoạt động.

+0

điều này có vẻ rực rỡ ... Tôi cần kiểm tra ASAP này để xem nó có hoạt động hay không. Thật không may, tôi đang sử dụng MSVC6, trong đó có vấn đề mẫu chính và IIRC nó sẽ barf trên templatized chức năng bên trong templatized lớp học, ngay cả khi bạn xác định rõ ràng các tham số mẫu khi gọi nó. – rmeador

+0

Thật sao? - Tôi biết tôi đã làm điều này chính xác dưới MSVC6. – Eclipse

+0

Tôi đã viết một lớp SmartPtr làm điều này ngay dưới MSVC6, vì vậy bạn sẽ không sao. –

2

Giả sử bạn có quyền kiểm soát của lớp SmartPtr, giải pháp là để cung cấp một constructor templated:

template <class T> 
class SmartPtr 
{ 
    T *ptr; 
public: 

    // Note that this IS NOT a copy constructor, just another constructor that takes 
    // a similar looking class. 
    template <class O> 
    SmartPtr(const SmartPtr<O> &src) 
    { 
     ptr = src.GetPtr(); 
    } 
    // And likewise with assignment operator. 
}; 

Nếu các loại T và O tương thích, nó sẽ làm việc, nếu họ không phải là bạn sẽ nhận được một lỗi biên dịch.

+0

Nó không phải lúc nào cũng đơn giản; bạn cần đảm bảo rằng số lượng tham chiếu thích hợp được duy trì. Bạn không thể xóa đối tượng Nguồn gốc sau khi Ptr cuối cùng biến mất khi vẫn còn Ptr cho cùng một Nguồn gốc. – MSalters

+0

Vâng, vâng, bạn sẽ vẫn phải đảm bảo rằng bạn thực sự triển khai các thông minh của con trỏ thông minh. – Eclipse

15

SmartPtr<Base>SmartPtr<Derived> là hai bản sao riêng biệt của mẫu SmartPtr. Các lớp mới này không chia sẻ thừa kế mà BaseDerived làm. Do đó, vấn đề của bạn.

what is the best way to perform this assignment operation?

SmartPtr<Base> b = d; 

Không gọi toán tử gán. Đây gọi là bản sao-ctor (bản sao được elided trong hầu hết các trường hợp) và là chính xác, nếu như bạn đã viết:

SmartPtr<Base> b(d); 

Cung cấp cho một bản sao-ctor mà phải mất một SmartPtr<OtherType> và thực hiện nó. Tương tự như vậy đối với toán tử gán. Bạn sẽ phải viết ra bản sao-ctor và op = ghi nhớ ngữ nghĩa của SmartPtr.

+0

Tôi biết rằng nó không hoạt động, và bây giờ tôi nhận thức lại (cảm ơn) rằng đó là vì sự thiếu quan hệ giữa hai lớp mẫu. Tôi hỏi làm thế nào để làm cho nó hoạt động như thể họ có liên quan, có lẽ bằng cách thêm một số ctor thông minh hoặc thủ đoạn khác? – rmeador

+2

Rất nhiều phụ thuộc vào ngữ nghĩa chính xác của lớp SmartPtr, ví dụ: nó có chuyển quyền sở hữu, nó làm tài liệu tham khảo đếm vv Bạn sẽ phải viết ra các bản sao-ctor và op = giữ trong tâm trí ngữ nghĩa của SmartPtr. – dirkgently

5

Mẫu không phải là biến thể và điều đó tốt; tưởng tượng điều gì sẽ xảy ra trong trường hợp sau đây:

vector<Apple*> va; 
va.push_back(new Apple); 

// Now, if templates were covariants, a vector<Apple*> could be 
// cast to a vector<Fruit*> 
vector<Fruit*> & vf = va; 
vf.push_back(new Orange); // Bam, we just added an Orange among the Apples! 

Để đạt được những gì bạn đang cố gắng để làm, lớp SmartPointer phải có một constructor templatized, mà sẽ đưa một trong hai khác SmartPointer hoặc một con trỏ kiểu khác. Bạn có thể có một cái nhìn tại boost :: shared_ptr, mà thực hiện chính xác điều đó.

template <typename T> 
class SmartPointer { 

    T * ptr; 

    public: 
    SmartPointer(T * p) : ptr(p) {} 
    SmartPointer(const SmartPointer & sp) : ptr(sp.ptr) {} 

    template <typename U> 
    SmartPointer(U * p) : ptr(p) {} 

    template <typename U> 
    SmartPointer(const SmartPointer<U> & sp) : ptr(sp.ptr) {} 

    // Do the same for operator= (even though it's not used in your example) 
}; 
+0

Bạn có một điểm tuyệt vời với ví dụ của bạn ... Tôi đã không xem xét điều đó. Tuy nhiên, trong trường hợp của một con trỏ thông minh, tôi không nghĩ rằng bạn gặp phải vấn đề đó bởi vì giao diện lớp cơ bản giống với giao diện của kiểu trỏ tới (thông qua quá tải -> và *). – rmeador

+0

Tôi nghĩ rằng ví dụ của bạn là một ví dụ sai ở chỗ nó không thực sự là biến thể chính xác _because_ bạn có thể xô một cái gì đó giống như một màu da cam trong danh sách. Nó sẽ chỉ có thể thay đổi nếu giao diện công cộng chỉ có _returns_ Fruit *, nhưng không phải là _accepts_ Fruit *. C# cung cấp từ khóa "in" và "out" để tạo các biến thể chung chung hoặc biến thể tương ứng. Nó thực thi tĩnh các kiểu được sử dụng như thế nào để tránh tình huống bạn mô tả. – LostSalad

0

Tôi nghĩ điều đơn giản nhất là để cung cấp chuyển đổi tự động khác SmartPtr theo như sau:

template <class T> 
class SmartPtr 
{ 
public: 
    SmartPtr(T *ptr) { t = ptr; } 
    operator T *() const { return t; } 
    template <class Q> operator SmartPtr<Q>() const 
    { return SmartPtr<Q>(static_cast<Q *>(static_cast<T *>(* this))); } 
private: 
    T *t; 
}; 

Lưu ý rằng việc thực hiện này là mạnh mẽ theo nghĩa là mẫu nhà điều hành chuyển đổi không cần phải biết về ngữ nghĩa của con trỏ thông minh, vì vậy việc đếm tham chiếu không cần sao chép, vv

+0

Hãy thử {return SmartPtr (t); } Trình biên dịch sẽ cho bạn biết nếu một T * có thể được gán cho Q * mà không có tất cả các phôi. Đảm bảo rằng logic đếm tham chiếu của bạn có thể chia sẻ số lượng tham chiếu giữa các loại mẫu. Số tham chiếu int * sẽ có thể. – jmucchiello