2009-04-07 29 views
8

Xét theo hai kịch bản sử dụng sau đây (chính xác như bạn nhìn thấy chúng, có nghĩa là, người dùng cuối sẽ chỉ quan tâm đến việc sử dụng Vector2_tVector3_t):Inheritance vs Chuyên

[1] Inheritance:

template<typename T, size_t N> struct VectorBase 
{ 
}; 

template<typename T> struct Vector2 : VectorBase<T, 2> 
{ 
}; 

template<typename T> struct Vector3 : VectorBase<T, 3> 
{ 
}; 

typedef Vector2<float> Vector2_t; 
typedef Vector3<float> Vector3_t; 

[2] Chuyên ngành:

template<typename T, size_t N> struct Vector 
{ 
}; 

template<typename T> struct Vector<T, 2> 
{ 
}; 

template<typename T> struct Vector<T, 3> 
{ 
}; 

typedef Vector<float, 2> Vector2_t; 
typedef Vector<float, 3> Vector3_t; 

tôi không thể làm cho tâm trí của tôi như là một giải pháp đẹp hơn. Lợi thế rõ ràng đối với thừa kế là sử dụng lại mã trong các lớp dẫn xuất; một bất lợi có thể là hiệu suất (kích thước lớn hơn, người dùng có thể vượt qua theo giá trị, vv). Chuyên ngành dường như tránh tất cả những điều đó, nhưng với chi phí phải tự mình lặp lại nhiều lần.

Tôi đã bỏ lỡ những ưu điểm/nhược điểm nào khác và theo ý kiến ​​của bạn, tôi nên đi tuyến đường nào?

Trả lời

12

gì cuối cùng bạn muốn, tôi nghĩ rằng, là phải có các loại người dùng

Vector<T, N> 

Và tùy thuộc vào N, người dùng sẽ có được những điều nhẹ khác nhau. Việc đầu tiên sẽ không thực hiện điều đó, nhưng thứ hai sẽ, về giá của mã trùng lặp.

Những gì bạn có thể làm là để đảo thừa kế:

template<typename T, size_t N> struct VectorBase 
{ 
}; 

template<typename T> struct VectorBase<T, 2> 
{ 
}; 

template<typename T> struct VectorBase<T, 3> 
{ 
}; 

template<typename T, size_t N> struct Vector : VectorBase<T, N> 
{ 
}; 

Và thực hiện vài chức năng mà chỉ phụ thuộc vào N là một số giá trị cụ thể trong cơ sở lớp thích hợp. Bạn có thể thêm một destructor được bảo vệ vào chúng, để ngăn chặn người dùng xóa các trường hợp của Vector thông qua con trỏ đến VectorBase (thông thường chúng thậm chí không thể đặt tên VectorBase: Đặt các cơ sở đó vào một không gian tên triển khai, chẳng hạn như detail).

Một ý tưởng khác là kết hợp giải pháp này với giải pháp được đề cập trong câu trả lời khác. Thừa kế riêng tư (thay vì công khai như trên) và thêm các hàm bao bọc vào lớp dẫn xuất, gọi các thực thi của lớp cơ sở.

Tuy nhiên, ý tưởng khác là sử dụng chỉ là một lớp và sau đó enable_if (sử dụng boost::enable_if) để kích hoạt hoặc vô hiệu hóa chúng cho các giá trị cụ thể của N, hoặc sử dụng một-to int biến áp kiểu như thế này là đơn giản hơn nhiều

struct anyi { }; 
template<size_t N> struct i2t : anyi { }; 

template<typename T, size_t N> struct Vector 
{ 
    // forward to the "real" function 
    void some_special_function() { some_special_function(i2t<N>()); } 

private: 
    // case for N == 2 
    void some_special_function(i2t<2>) { 
     ... 
    } 

    // case for N == 3 
    void some_special_function(i2t<3>) { 
     ... 
    } 

    // general case 
    void some_special_function(anyi) { 
     ... 
    } 
}; 

Bằng cách đó, nó hoàn toàn minh bạch cho người dùng của Vector. Nó cũng sẽ không thêm bất kỳ không gian trên không cho các trình biên dịch làm tối ưu hóa lớp cơ sở trống (khá phổ biến).

+0

câu trả lời hoàn chỉnh nhất. Cảm ơn bạn! –

+0

Câu trả lời tuyệt vời và COOL. Đưa một guru để có được một câu trả lời thanh lịch như vậy. –

+0

+1. Tôi nghi ngờ đề xuất của bạn về enable_if <> trong một mẫu lớp đơn là cách tốt nhất - chắc chắn sự khác biệt duy nhất giữa các lớp mẫu cho các tham số vector khác nhau sẽ là số tham số trong hàm tạo? –

0

Nếu bạn đang sử dụng chuyên môn mẫu quá nhiều, có thể bạn cần phải suy nghĩ lại về thiết kế của mình. Xem xét rằng bạn đang ẩn nó đằng sau một typedef, tôi nghi ngờ bạn cần nó.

4

Sử dụng thừa kế thừa kế và riêng tư. Và không sử dụng bất kỳ chức năng ảo nào. Vì với thừa kế riêng, bạn không có-a, không ai có thể sử dụng một con trỏ baseclas đến một lớp con có nguồn gốc, và bạn sẽ không gặp vấn đề về slicing khi passinfg theo giá trị.

Điều này mang lại cho bạn tốt nhất của cả hai thế giới (và thực sự đó là cách hầu hết các thư viện triển khai nhiều lớp STL).

Từ http://www.hackcraft.net/cpp/templateInheritance/ (thảo luận std :: vector, không phải của bạn V ector lớp):

vector<T*> được khai báo để có một cơ sở tư nhân của vector<void*>. Tất cả các chức năng mà đặt một yếu tố mới vào vector, chẳng hạn như push_back(), hãy gọi chức năng tương đương trên cơ sở tư nhân này, nên trong nội bộ của chúng tôi vector<T*> đang sử dụng một vector<void*> để lưu trữ. Tất cả các chức năng trả về một phần tử từ vectơ, chẳng hạn như front(), thực hiện static_cast trên kết quả gọi hàm tương đương trên cơ sở riêng. Kể từ khi cách duy nhất để có được một con trỏ vào vector<void*> (ngoài thủ đoạn cố tình nguy hiểm) là thông qua giao diện được cung cấp bởi vector<T*> nó là an toàn để staticly cast void* trở lại T* (hoặc void*& trở lại T*&, và Sớm).

Nói chung, nếu STL thực hiện như thế này, nó có vẻ giống như một mô hình tốt để mô phỏng.

+0

Đó là một điểm tốt, tôi hoàn toàn quên về STL. –

+0

Chọn nit: Nói đúng là bạn có "là", nhưng "đó là" không công khai. Ví dụ. một 'std: vector ' là một 'std: vector ' như một phương tiện để thực hiện những thứ có hiệu quả không gian tốt. Nhưng đó là "là một" tư nhân, như xa như bất kỳ mã bên ngoài là có liên quan, 'std: vectơ ' không phải là một 'std: vectory ' bởi vì nó không thể "nhìn thấy" mối quan hệ. Vì vậy, nghiêm chỉnh bạn có là-a, nhưng trong thực tế bạn không :) –

0

Chỉ nên sử dụng thừa kế để mô hình "is-a". Chuyên môn sẽ là giải pháp thay thế sạch hơn. Nếu bạn cần hoặc muốn sử dụng thừa kế vì bất kỳ lý do gì, ít nhất là làm cho nó trở thành private hoặc protected inheritance, vì vậy bạn không kế thừa công khai từ một class với một destructor không ảo.

Vâng, những kẻ mẫu Lập trình meta luôn làm

struct something : something_else {}; 

Nhưng những something s là metafunctions và không có nghĩa là để được sử dụng như các loại.