วิธีกำหนดประเภทต่างๆสำหรับคลาสเดียวกันใน C ++


84

ฉันต้องการมีหลายประเภทที่แชร์การใช้งานเดียวกัน แต่ยังคงเป็นประเภทที่แตกต่างกันใน C ++

เพื่ออธิบายคำถามของฉันด้วยตัวอย่างง่ายๆฉันต้องการมีคลาสสำหรับแอปเปิ้ลส้มและกล้วยซึ่งทั้งหมดมีการดำเนินการเหมือนกันและการนำไปใช้งานเหมือนกัน ฉันต้องการให้พวกเขามีประเภทต่างๆเพราะฉันต้องการหลีกเลี่ยงข้อผิดพลาดเนื่องจากความปลอดภัยของประเภท

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 ...

เพื่อไม่ให้รหัสซ้ำดูเหมือนว่าฉันสามารถใช้ผลไม้คลาสพื้นฐานและสืบทอดจากมันได้:

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 {};

แต่แล้วคอนสตรัคเตอร์ไม่ได้รับการสืบทอดและฉันต้องเขียนมันใหม่

มีกลไกใดบ้าง (typedefs, template, inheritance ...


คุณสามารถอธิบายรายละเอียดเพิ่มเติมได้ไหมว่าทำไมคุณถึงต้องการสิ่งนี้? ฉันไม่สามารถคิดอะไรดีๆได้ ถ้าชั้นเรียนใช้งานร่วมกันนั่นหมายความว่าพวกเขาแบ่งปันฟังก์ชันการทำงานด้วยหรือ
jnovacho

4
ใช่ แต่เนื่องจากจะมีประเภทที่แตกต่างกันจึงสามารถตรวจพบข้อผิดพลาดในการเขียนโปรแกรมบางอย่างได้ในเวลารวบรวม (เช่นการรวมแอปเปิ้ลและส้ม)
anumi

คำตอบ:


119

เทคนิคทั่วไปคือการมีเทมเพลตคลาสที่อาร์กิวเมนต์เทมเพลตทำหน้าที่เป็นโทเค็นเฉพาะ ("แท็ก") เพื่อทำให้เป็นประเภทที่ไม่ซ้ำกัน:

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>;

โปรดทราบว่าคลาสแท็กไม่จำเป็นต้องกำหนดด้วยซ้ำ แต่ก็เพียงพอที่จะประกาศชื่อประเภทที่ไม่ซ้ำกัน สิ่งนี้ได้ผลเนื่องจากแท็กไม่ได้ถูกนำไปใช้จริงที่ใดก็ได้ในเทมเพลต และคุณสามารถประกาศชื่อประเภทภายในรายการอาร์กิวเมนต์ของเทมเพลต (คำแนะนำสำหรับ @Xeo)

usingไวยากรณ์คือ C ++ 11 หากคุณติด C ++ 03 ให้เขียนสิ่งนี้แทน:

typedef Fruit<struct AppleTag> Apple;

หากฟังก์ชันทั่วไปใช้รหัสจำนวนมากสิ่งนี้น่าเสียดายที่จะแนะนำรหัสที่ซ้ำกันจำนวนมากในไฟล์ปฏิบัติการขั้นสุดท้าย สิ่งนี้สามารถป้องกันได้โดยมีคลาสพื้นฐานทั่วไปที่ใช้ฟังก์ชันการทำงานจากนั้นมีความเชี่ยวชาญพิเศษ (ที่คุณสร้างอินสแตนซ์จริง) ที่มาจากมัน

น่าเสียดายที่คุณต้องนำสมาชิกที่ไม่สามารถสืบทอดได้ทั้งหมดมาใช้ใหม่ (ตัวสร้างการมอบหมายงาน ... ) ซึ่งจะเพิ่มค่าใช้จ่ายเล็กน้อยด้วยตัวมันเองดังนั้นสิ่งนี้จึงเหมาะสมสำหรับชั้นเรียนขนาดใหญ่เท่านั้น ที่นี่ใช้กับตัวอย่างข้างต้น:

// 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 ฉันจะไปกับสิ่งนี้ถ้าคุณไม่ต้องการให้มีคุณสมบัติเพิ่มเติมใด ๆ ที่กำหนดไว้สำหรับผลไม้แต่ละชนิด ...
นิ่ม

20
Fruit<struct SomeTag>คุณสามารถจริงเพียงแค่ประกาศให้ภายในรายการแม่แบบอาร์กิวเมนต์ที่ฉันพบความสะดวกสบายสวย:
Xeo

1
@KonradRudolph มันเป็นความอัปยศฉันไม่สามารถ +1 แก้ไขตัวเอง ..... ผมเห็นว่าการแก้ไขความคิดเห็น
รันดร์มัต

1
@eternalmatt LOL - ฉันไม่เคยคิดเลยว่าจะมีใครเห็นแบบนั้น แต่คุณต้องตลกแม้ว่าจะไม่มีใครมอง ;-)
Konrad Rudolph

2
ข้อเสียคือการปล่อยเทมเพลตอินสแตนซ์ของเทมเพลตหลายรายการสำหรับประเภทต่างๆ รายการที่ซ้ำกันเหล่านี้ถูกกำจัดโดยตัวเชื่อมโยงที่ใช้กันอย่างแพร่หลายหรือไม่?
boycy

19

ใช้เทมเพลตและใช้ลักษณะต่อผลไม้เช่น:

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

จากนั้นมีFruitคลาสเดียวที่พิมพ์ในลักษณะนี้เช่น

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;

อาจจะ overkill เล็กน้อย! ;)



โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.