C++, การสืบทอดเสมือน, ปัญหาคลาสนามธรรมแปลก ๆ + โคลน

ขออภัยสำหรับซอร์สโค้ดจำนวนมากขึ้น มีคลาสนามธรรมสามคลาส P, L, PL PL คลาสที่สามได้มาจากคลาส P และ L โดยใช้การสืบทอดเสมือน:

template <typename T>  //Abstract class
class P
{

    public:
            virtual ~P() = 0;
    virtual P <T> *clone() const  = 0;
};

template <typename T>
P<T>::~P() {}

template <typename T>
P<T> * P <T>:: clone() const { return new P <T> ( *this ); }

template <typename T>  //Abstract class
class L
{
    public:
            virtual ~L() = 0;
    virtual L <T> *clone() const = 0;
};

template <typename T>
L<T>::~L() {}

template <typename T>
L<T> *L <T> ::clone() const { return new L <T> ( *this );}

template <typename T>
class PL: virtual public P <T>, virtual public L <T>  //Abstract class
{
    public:
            PL() : P <T>(), L<T>() {}
    virtual ~PL() = 0;
    virtual PL <T> *clone() const  = 0;
};

template <typename T>
PL<T>::~PL() {}

template <typename T>
PL<T> * PL <T> :: clone() const { return new PL <T> ( *this );}

ทุกคลาสมีการนำเมธอดโคลนไปใช้เป็นของตัวเอง

PA, PC สองคลาสถัดไปได้มาจากคลาส P โดยใช้การสืบทอดเสมือน:

template <typename T>
class PC : virtual public P <T>
{
    public:
            PC() : P <T> () {}
            virtual ~PC() {}
            virtual PC <T> *clone() const {return new PC <T> ( *this );}
};

template <typename T>
class PA : virtual public P <T>
{
    public:
            PA() : P <T> () {}
            virtual ~PA() {}
            virtual PA <T> *clone() const {return new PA <T> ( *this );}
};

สองคลาสสุดท้าย PCL และ PAL ได้รับการสืบทอดโดยใช้การสืบทอดเสมือนจาก PC และ PL , PA และ PL

template <typename T>
class PCL : public PC <T>, public PL <T>
{
    public:
            PCL() : P <T> (), PC <T> (), PL <T> ()  {}
            virtual ~PCL() {}
            virtual PCL <T> *clone() const {return new PCL <T> ( *this );}
};

template <typename T>
class PAL : public PA <T>, public PL <T>
{
    public:
            PAL() : P <T> (), PA <T> (), PL <T> () {}
            virtual ~PAL() {}
            virtual PAL <T> *clone() const {return new PAL <T> ( *this );}

};

มีไดอะแกรมของการขึ้นต่อกันของคลาส:

.......... P .... L.....
........../|\..../......
........./.|.\../.......
......../..|..\/........
.......PC..PA..PL.......
.......|...|.../|.......
.......|...|../.|.......
.......|...PAL..|.......
.......|........|.......
.......PCL_____/........  

กรุณาอย่าหารือเกี่ยวกับข้อเสนอนี้ :-))) ฉันมีคำถาม 3 ข้อต่อไปนี้:

1) การพึ่งพาคลาสนี้เขียนใหม่ในภาษา C ++ อย่างถูกต้อง (ก่อนอื่นคือการวางตำแหน่ง "เสมือน") หรือไม่

2) ฉันไม่แน่ใจว่ามีอะไรผิดปกติ ดูรหัส โปรด:

int main(int argc, _TCHAR* argv[])
{
PCL <double> * pcl = new PCL <double>(); //Object of abstract class not allowed
PAL <double> * pal = new PAL <double>(); //Object of abstract class not allowed
PL <double> *pl1 = pcl->clone(); //Polymorphism
PL <double> *pl2 = pal->clone(); //Polymorphism
return 0;
} 

ไม่สามารถสร้างวัตถุใหม่ของคลาส PAL / PCL ได้ ทั้งสองคลาสจะถูกทำเครื่องหมายว่าเป็นนามธรรม แต่พวกมันไม่ใช่นามธรรม ปัญหาอยู่ที่ไหน?

3) เป็นไปได้ไหมที่จะใช้ polymorphism ร่วมกับวิธี clone() ดูรหัสด้านบนกรุณา...

ขอบคุณสำหรับความช่วยเหลือของคุณ...


คำถามที่อัปเดต

ฉันแก้ไขรหัสแล้ว แต่ข้อผิดพลาดต่อไปนี้ที่ใช้คอมไพเลอร์ VS 2010 ปรากฏขึ้น:

template <typename T>  //Abstract class
class P
{
    public:
    virtual ~P() = 0;
    virtual P <T> *clone() const  = 0;
};

template <typename T>
P<T>::~P() {}

template <typename T>
P<T> * P <T>:: clone() const { return new P <T> ( *this ); }


template <typename T>  //Abstract class
class L
{
    public:
    virtual ~L() = 0;
    virtual L <T> *clone() const = 0;
};

template <typename T>
L<T>::~L() {}

template <typename T>
L<T> *L <T> ::clone() const { return new L <T> ( *this );}


template <typename T>
class PL: virtual public P <T>, virtual public L <T>  //Abstract class
{
    public:
            PL() : P <T>(), L<T>() {}
    virtual ~PL() = 0;
    virtual PL <T> *clone() const  = 0; 
};

template <typename T>
PL<T>::~PL() {}

template <typename T>
PL<T> * PL <T> :: clone() const { return new PL <T> ( *this );}


template <typename T>
class PC : virtual public P <T>
{
    protected:
            T pc;
    public:
            PC() : P <T> () {}
    virtual ~PC() {}
            virtual PC <T> *clone() const {return new PC <T> ( *this );}
};

template <typename T>
class PA : virtual public P <T>
{
    public:
            PA() : P <T> () {}
    virtual ~PA() {}
            virtual PA <T> *clone() const {return new PA <T> ( *this );}
};

template <typename T>
class PCL : public PC <T>, public PL <T>
{
public:
            PCL() : P <T> (), PC <T> (), PL <T> () {}
            virtual ~PCL() {}
            virtual PCL <T> *clone() const {return new PCL <T> ( *this   );}
}; //Error using VS 2010: Error 1   error C2250: 'PCL<T>' : ambiguous inheritance of 'PC<T> *P<T>::clone(void) const'


template <typename T>
class PAL : public PA <T>, public PL <T>
{
public:
            PAL() : P <T> (), PA <T> (), PL <T> ()  {}
            virtual ~PAL() {}
            virtual PAL <T> *clone() const {return new PAL <T> ( *this );}
}; //Error VS 2010: Error   1   error C2250: 'PAL<T>' : ambiguous inheritance of 'PA<T> *P<T>::clone(void) const'


int main(int argc, char* argv[])
{
PCL <double> * pcl = new PCL <double>();
PAL <double> * pal = new PAL <double>();
PL <double> *pl1 = pcl->clone();
PL <double> *pl2 = pal->clone();
return 0;
}

บางทีฉันอาจมองข้ามบางสิ่งบางอย่างไป ... แต่ g++ คอมไพล์โค้ดนี้ก็โอเค


person Johnas    schedule 03.08.2011    source แหล่งที่มา
comment
ตัวทำลายเสมือนบริสุทธิ์ของคุณใน P, L และ PL มีการใช้งานเริ่มต้นหรือไม่   -  person Chad    schedule 03.08.2011
comment
ฉันคิดว่าคุณไม่สามารถเพิ่มคำจำกัดความให้กับการประกาศฟังก์ชันล้วนๆ ได้ พูด = 0; นอกจากนี้ ยังมีวงเล็บหายไปหลังจาก PL <T> { } (ควรเป็น PL <T>() { }) ดังที่ @Chad กล่าวไว้ คุณไม่จำเป็นต้องสร้าง destructors เสมือนบริสุทธิ์ หากคุณมีฟังก์ชันเสมือนอื่น ๆ อยู่แล้ว   -  person Kerrek SB    schedule 03.08.2011
comment
@Chad: ใช่ ฉันแก้ไขรหัสแล้ว...   -  person Johnas    schedule 03.08.2011


คำตอบ (5)


สิ่งนี้คอมไพล์สำหรับฉันโดยใช้ VC10:

template <typename T>  //Abstract class
class P
{
public:
    virtual ~P() = 0;
    virtual P <T> *clone() const  = 0;
};

template <typename T>
P<T>::~P() {}


template <typename T>  //Abstract class
class L
{
public:
    virtual ~L() = 0;
    virtual L <T> *clone() const = 0;
};

template <typename T>
L<T>::~L() {}


template <typename T>
class PL: virtual public P <T>, virtual public L <T>  //Abstract class
{
public:
    PL() : P <T>(), L<T>() {}
    virtual ~PL() = 0;
//  virtual PL <T> *clone() const  = 0; // REMOVED!
};

template <typename T>
PL<T>::~PL() {}


template <typename T>
class PC : virtual public P <T>
{
protected:
    T pc;
public:
    PC() : P <T> () {}
    virtual ~PC() {}
    virtual PC <T> *clone() const {return new PC <T> ( *this );}
};

template <typename T>
class PA : virtual public P <T>
{
public:
    PA() : P <T> () {}
    virtual ~PA() {}
    virtual PA <T> *clone() const {return new PA <T> ( *this );}
};

template <typename T>
class PCL : public PC <T>, public PL <T>
{
public:
    PCL() : P <T> (), PC <T> (), PL <T> () {}
    virtual ~PCL() {}
    virtual PCL <T> *clone() const {return new PCL <T> ( *this   );}
};


template <typename T>
class PAL : public PA <T>, public PL <T>
{
public:
    PAL() : P <T> (), PA <T> (), PL <T> ()  {}
    virtual ~PAL() {}
    virtual PAL <T> *clone() const {return new PAL <T> ( *this );}
};


int main()
{
    PCL <double> * pcl = new PCL <double>();
    PAL <double> * pal = new PAL <double>();
    PL <double> *pl1 = pcl->clone();
    PL <double> *pl2 = pal->clone();
    return 0;
}

โปรดทราบว่าฉันต้องลบ PL <T> *PL <T>::clone() เพื่อให้ VC ยอมรับรหัส ปัญหาก็คือ หากคุณมี PL<T> และเรียกมันว่า clone() มันจะส่งคืน L<T>* แบบคงที่ แทนที่จะเป็น PL<T>* แม้ว่าประเภทไดนามิกจะเป็นคลาสที่ได้มาจาก PL<T> ก็ตาม

person sbi    schedule 03.08.2011
comment
โอเค มันได้ผล แต่คุณลบคำจำกัดความทั้งหมดของวิธีการโคลนในคลาสนามธรรมสามคลาส - person Johnas; 03.08.2011
comment
@Johnas: วิธีการเหล่านี้พยายามสร้างอินสแตนซ์ของวัตถุในคลาส นามธรรม ของตัวเอง นั่นจะไม่มีวันได้ผลอย่างแน่นอน แต่คุณไม่ต้องการมันอยู่แล้ว เพราะคุณจะไม่มี อ็อบเจ็กต์ ของคลาสเหล่านี้ (เพราะว่ามันเป็นนามธรรม) คุณจะมีเพียงอ็อบเจ็กต์ของคลาสที่ได้รับเท่านั้นเสมอ และคลาสเหล่านั้นจะใช้ clone() จริงๆ แล้ว ปัญหาเดียวของโค้ดจากคำตอบของฉันคือตามที่ฉันได้เขียนไว้ในคำตอบใต้โค้ด - person sbi; 03.08.2011
comment
ฉันลืมไปว่าฉันสามารถสร้างตัวชี้ไปยังคลาสนามธรรมได้ แต่ไม่ใช่วัตถุ (หรือไดนามิก)... ขอบคุณสำหรับความช่วยเหลือของคุณ... - person Johnas; 03.08.2011

มีข้อผิดพลาดเล็กน้อยในรหัสของคุณ:

  • รับค่าเริ่มต้นฐานให้ถูกต้อง: PAL() : PC <T>(), PL <T>() {} ไม่มีการกำหนดค่าเริ่มต้นเป็น P<T> ซึ่งไม่ใช่ฐานโดยตรง แต่[ขออภัย นั่นผิด -- คุณ ทำ ต้องเรียกตัวสร้างฐานเสมือนเนื่องจากการสืบทอดเสมือน] คุณต้องพูดในวงเล็บกลม

  • ประกาศฟังก์ชันเสมือนล้วนๆ โดยไม่มีคำจำกัดความ: virtual L <T> *clone() const = 0;

  • ตรวจสอบอีกครั้งว่าคุณต้องการให้ PL สืบทอดแบบเสมือนจาก P แต่ไม่ใช่แบบเสมือนจาก L (แต่ก็ไม่เป็นไร)

  • ตรวจสอบอีกครั้งว่า clone()s ทั้งหมดของคุณมีความสม่ำเสมอเท่ากัน

  • หากคุณมีฟังก์ชันเสมือนบริสุทธิ์หนึ่งฟังก์ชันในคลาสฐานนามธรรมของคุณ คุณไม่ควรทำให้ destructor เป็นฟังก์ชันบริสุทธิ์ ให้พูดว่า virtual ~T() { } แทน มิฉะนั้น คุณยังควรระบุคำจำกัดความของตัวทำลายเสมือนบริสุทธิ์ (เพราะตัวทำลายล้างจำเป็นต้องสามารถเรียกได้เสมอ): virtual T::~T() { }

person Kerrek SB    schedule 03.08.2011
comment
แท้จริงแล้วข้อผิดพลาดที่แท้จริงต้องมาจาก clone() ไม่ใช่ const ideone.com/3bTty เป็นโค้ดเวอร์ชันที่ได้รับการล้างข้อมูลแล้ว และจะคอมไพล์ (แต่ไม่ใช่ลิงก์) เนื่องจากโค้ดมีข้อผิดพลาดมากมาย จึงเป็นที่น่าสงสัยว่าสอดคล้องกับโค้ดที่ OP ประสบปัญหาจริงๆ หรือไม่ - person PlasmaHH; 03.08.2011
comment
จริงๆ แล้วฉันไม่ได้รับคำเตือนหรือข้อผิดพลาดใดๆ เกี่ยวกับความไม่แน่นอน ฉันไม่แน่ใจ 100% ในขณะนี้ แต่มีความเป็นไปได้ที่ การซ่อน ที่ผิดพลาดจะเกิดขึ้นแทนที่จะแทนที่ ฉันไม่ต้องการใช้เวลากับสิ่งนี้มากขึ้นในตอนนี้ แต่สิ่งสำคัญมากคือต้องได้รับลายเซ็นให้ถูกต้องเมื่อแทนที่ - person Kerrek SB; 03.08.2011
comment
ฉันไม่คาดหวังคำเตือนหรือข้อผิดพลาดใด ๆ เกี่ยวกับความสับสนเช่นกัน มันเป็นเพียงลายเซ็นอื่น เพียงแค่เห็นว่ามันเป็นการพิมพ์ผิดในชื่อฟังก์ชัน - person PlasmaHH; 03.08.2011
comment
แล้วมีปัญหาอะไรล่ะ? ฉันรวบรวมได้เรียบร้อย มีคำถามอื่นอีกไหม? - person Kerrek SB; 03.08.2011
comment
ฉันได้รับข้อผิดพลาดต่อไปนี้: ข้อผิดพลาด 1 ข้อผิดพลาด C2250: 'PCL‹T›' : มรดกที่คลุมเครือของ 'PC‹T› *P‹T›::clone(void) const'... โปรดดูรหัสที่อัปเดต .. ; - person Johnas; 03.08.2011
comment
ขอบคุณสำหรับความอดทนของคุณ. แต่ฉันไม่เห็นปัญหาอื่นใดบางทีฉันอาจจะตาบอด :-) รหัสนี้ไม่ได้คอมไพล์โดยใช้ MSVS แต่ใช้ได้กับ g++.. - person Johnas; 03.08.2011
comment
ฉันสร้างฐานเสมือนผิด คุณมีสิทธิ์ที่จะมี P<T>() ในเครื่องมือเริ่มต้น ฉันไม่รู้ มันคอมไพล์ได้ดีใน GCC พร้อมคำเตือนทั้งหมด คุณมี MSVS เวอร์ชันใด 2010SP1? - person Kerrek SB; 03.08.2011
comment
ฉันใช้ MSVS2010 sp1 ฉันโพสต์โค้ดเวอร์ชันสุดท้ายในคำถามที่อัปเดตแล้ว... คุณช่วยตรวจสอบได้ไหม - person Johnas; 03.08.2011
comment
ขออภัย ฉันมีเพียง GCC เท่านั้น! บางทีคนอื่นที่นี่อาจลองดูก็ได้ - person Kerrek SB; 03.08.2011
comment
อืม ด้วยความอยากรู้อยากเห็น และไม่ใช่ว่าคุณควรจะต้องทำ จะเกิดอะไรขึ้นถ้าคุณทำให้ PAL และ PCL สืบทอดแบบเสมือนจริง? - person Kerrek SB; 03.08.2011

MSVC ไม่รองรับการส่งคืนตัวแปรร่วมอย่างถูกต้อง ที่คุณคิดว่าคุณได้เอาชนะฟังก์ชันนี้ จริงๆ แล้วคุณก็แค่ซ่อนมันไว้ นั่นคือปัญหาที่อยู่ในมือ

person Puppy    schedule 03.08.2011
comment
@Johnas: คุณสามารถลบ PL <T> *PL <T>::clone() ได้ไหม เพราะงั้นมันก็จะคอมไพล์ให้ฉัน (นั่นคือเมื่อฉันลบการใช้งานฟังก์ชัน clone() เสมือนล้วนๆ ที่พยายามสร้างอินสแตนซ์คลาสนามธรรม) - person sbi; 03.08.2011
comment
คุณช่วยแนะนำวิธีแก้ไขโค้ดให้แปลโดยใช้ MSVS 2010 ได้ไหม - person Johnas; 03.08.2011
comment
@Johnas: อย่างที่ฉันพูดไปมันจะคอมไพล์ให้ฉันถ้าฉันลบ PL <T> *PL <T>::clone() - person sbi; 03.08.2011
comment
ฉันส่งโพสต์ของฉันในเวลาเดียวกันเช่นคุณ :-) หลังจากลบ PL‹T› * PL ‹T› :: clone() const { return new PL ‹T› ( *this );} ไม่มีอะไรเปลี่ยนแปลง ข้อผิดพลาดเดิม ... - person Johnas; 03.08.2011
comment
@Johnas: อย่าเพิ่งลบ คำจำกัดความ ของฟังก์ชันสมาชิก แต่ยังลบ การประกาศ ด้วย สิ่งนี้รวบรวมสำหรับฉัน - person sbi; 03.08.2011
comment
@Johnas: ดูคำตอบของฉัน - person sbi; 03.08.2011
comment
ฉันขอโทษ ฉันไม่เข้าใจสิ่งที่คุณหมายถึง... PL ‹T› *PL ‹T›::clone() เป็นส่วนหนึ่งของคำจำกัดความ การประกาศนั้นเป็นเสมือน PL ‹T› *clone() const = 0 ... - person Johnas; 03.08.2011

เนื่องจากวิธีการโคลนในคลาส P ของคุณนั้นเป็นนามธรรมตามที่กำหนดไว้

virtual P <T> *clone() const = 0 {};

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


แก้ไข: สำหรับคำถามที่สาม ความแตกต่างของรันไทม์และเวลาในการคอมไพล์ไม่เข้ากัน ฉันไม่รู้ว่าทำไมบนโลกนี้คุณถึงต้องการใช้โครงสร้างที่ซับซ้อนเช่นนี้ แต่ฉันแน่ใจว่าการออกแบบนั้นสามารถทำให้ง่ายขึ้นได้มาก

person Osada Lakmal    schedule 03.08.2011
comment
ดังนั้น นี่เป็นวิธีการเชิงนามธรรมล้วนๆ ที่ไม่เคยถูกนำมาใช้ ค่อนข้างไม่ถูกต้อง เนื่องจากฟังก์ชันเหล่านั้นไม่ใช่เทมเพลตสมาชิก ประเภทการส่งคืนจะขึ้นอยู่กับพารามิเตอร์เทมเพลตของเทมเพลตคลาส ซึ่งถือว่าใช้ได้ นอกจากนี้ผลตอบแทนร่วมของการใช้งานคลาสที่ได้รับนั้นก็โอเค แม้ว่าจะสังเกตได้ยาก (ซึ่งเป็นหนึ่งในหลาย ๆ เหตุผลที่เราควรคิดใหม่เกี่ยวกับการออกแบบนั้น) - person PlasmaHH; 03.08.2011

  1. มีการพิมพ์ผิดสองสามอย่างที่ต้องแก้ไขในโค้ดของคุณ แต่ virtuals ใน inheritance lattice นั้นถูกต้อง (เป็นจำนวนขั้นต่ำสุด คุณสามารถเพิ่มมากขึ้นได้ ในกรณีที่พบไม่บ่อยซึ่งเกิดขัดแตะเช่นนี้ บ่อยครั้งวิธีที่ดีที่สุดคือทำให้มรดกทั้งหมดเป็นเสมือน เพื่อป้องกันวิวัฒนาการในอนาคต)

  2. ไม่มีอะไรผิดปกติกับรหัสทดสอบของคุณ เมื่อแก้ไขคำผิดในต้นฉบับแล้ว มันจะคอมไพล์ได้ดีด้วย g++

  3. เป็นไปได้และควรทำงานในแบบที่คุณทำ

person James Kanze    schedule 03.08.2011