Cách xác định các kiểu khác nhau cho cùng một lớp trong C ++


84

Tôi muốn có một số kiểu chia sẻ cùng một cách triển khai nhưng vẫn thuộc kiểu khác nhau trong C ++.

Để minh họa câu hỏi của tôi bằng một ví dụ đơn giản, tôi muốn có một lớp dành cho Táo, Cam và Chuối, tất cả đều có cùng các thao tác và cách thực hiện giống nhau. Tôi muốn chúng có nhiều kiểu khác nhau vì tôi muốn tránh lỗi nhờ vào an toàn kiểu.

class Apple {
     int p;
public:
     Apple (int p) : p(p) {}
     int price () const {return p;}
}

class Banana {
     int p;
public:
     Banana (int p) : p(p) {}
     int price () const {return p;}
}

class Orange ...

Để không trùng lặp mã, có vẻ như tôi có thể sử dụng Fruit lớp cơ sở và kế thừa từ nó:

class Fruit {
     int p;
public:
     Fruit (int p) : p(p) {}
     int price () const {return p;}
}

class Apple: public Fruit {};
class Banana: public Fruit {};
class Orange: public Fruit {};

Nhưng sau đó, các hàm tạo không được kế thừa và tôi phải viết lại chúng.

Có cơ chế nào (typedefs, mẫu, kế thừa ...) cho phép tôi dễ dàng có cùng một lớp với các kiểu khác nhau không?


Bạn có thể giải thích chi tiết hơn tại sao bạn cần điều này? Tôi không thể nghĩ ra bất kỳ ý tưởng hay. Nếu các lớp chia sẻ việc triển khai, không có nghĩa là chúng cũng chia sẻ chức năng?
jnovacho

4
Có, nhưng vì chúng sẽ có các kiểu khác nhau, một số lỗi lập trình có thể được phát hiện tại thời điểm biên dịch (ví dụ: hợp nhất Táo và Cam).
anumi

Câu trả lời:


119

Một kỹ thuật phổ biến là có một mẫu lớp trong đó đối số mẫu chỉ đóng vai trò như một mã thông báo duy nhất (“thẻ”) để làm cho nó trở thành một loại duy nhất:

template <typename Tag>
class Fruit {
    int p;
public:
    Fruit(int p) : p(p) { }
    int price() const { return p; }
};

using Apple = Fruit<struct AppleTag>;
using Banana = Fruit<struct BananaTag>;

Lưu ý rằng các lớp thẻ thậm chí không cần phải được xác định, nó đủ để khai báo một tên kiểu duy nhất. Điều này hoạt động vì thẻ thực sự không được sử dụng ở bất kỳ đâu trong mẫu. Và bạn có thể khai báo tên kiểu bên trong danh sách đối số mẫu (tip cho @Xeo).

Các usingcú pháp là C ++ 11. Nếu bạn gặp khó khăn với C ++ 03, hãy viết điều này thay thế:

typedef Fruit<struct AppleTag> Apple;

Nếu chức năng thông thường chiếm nhiều mã, điều này không may sẽ tạo ra khá nhiều mã trùng lặp trong tệp thực thi cuối cùng. Điều này có thể được ngăn chặn bằng cách có một lớp cơ sở chung triển khai chức năng và sau đó có một chuyên môn hóa (mà bạn thực sự khởi tạo) có được từ nó.

Thật không may, điều đó đòi hỏi bạn phải triển khai lại tất cả các thành viên không thể kế thừa (hàm tạo, phép gán…) mà tự nó thêm một chi phí nhỏ - vì vậy điều này chỉ có ý nghĩa đối với các lớp lớn. Ở đây nó được áp dụng cho ví dụ trên:

// Actual `Fruit` class remains unchanged, except for template declaration
template <typename Tag, typename = Tag>
class Fruit { /* unchanged */ };

template <typename T>
class Fruit<T, T> : public Fruit<T, void> {
public:
    // Should work but doesn’t on my compiler:
    //using Fruit<T, void>::Fruit;
    Fruit(int p) : Fruit<T, void>(p) { }
};

using Apple = Fruit<struct AppleTag>;
using Banana = Fruit<struct BananaTag>;

+1, tôi sẽ đi với điều này nếu bạn không muốn có bất kỳ thuộc tính bổ sung nào được xác định cho từng loại trái cây riêng lẻ ...
Nim

20
Bạn có thể thực sự chỉ cần khai báo chúng bên trong danh sách mẫu tranh luận, mà tôi tìm thấy khá thuận tiện: Fruit<struct SomeTag>.
Xeo

1
@KonradRudolph, thật tiếc là tôi không thể +1 bản chỉnh sửa ..... Tôi đã xem nhận xét chỉnh sửa đó .
forevermatt

1
@eternalmatt LOL - Tôi sẽ không bao giờ nghĩ rằng có ai đó sẽ thấy điều đó. Nhưng tốt, bạn phải hài hước ngay cả khi không ai nhìn. ;-)
Konrad Rudolph

2
Nhược điểm của điều này là nhiều lần phát tán khuôn mẫu cho các loại khác nhau. Các bản sao này có bị loại bỏ bởi các trình liên kết được sử dụng rộng rãi không?
boycy

19

Sử dụng các mẫu và sử dụng một đặc điểm trên mỗi loại quả, ví dụ:

struct AppleTraits
{
  // define apple specific traits (say, static methods, types etc)
  static int colour = 0; 
};

struct OrangeTraits
{
  // define orange specific traits (say, static methods, types etc)
  static int colour = 1; 
};

// etc

Sau đó, có một Fruitlớp duy nhất được đánh vào đặc điểm này, ví dụ.

template <typename FruitTrait>
struct Fruit
{
  // All fruit methods...
  // Here return the colour from the traits class..
  int colour() const
  { return FruitTrait::colour; }
};

// Now use a few typedefs
typedef Fruit<AppleTraits> Apple;
typedef Fruit<OrangeTraits> Orange;

Có thể hơi quá mức cần thiết! ;)



Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.