ทำไมเราไม่สามารถประกาศ std :: vector <AbstractClass>


89

หลังจากใช้เวลาในการพัฒนา C # อยู่พอสมควรฉันสังเกตเห็นว่าหากคุณประกาศคลาสนามธรรมเพื่อใช้เป็นอินเทอร์เฟซคุณจะไม่สามารถสร้างอินสแตนซ์เวกเตอร์ของคลาสนามธรรมนี้เพื่อเก็บอินสแตนซ์ของคลาสเด็กได้

#pragma once
#include <iostream>
#include <vector>

using namespace std;

class IFunnyInterface
{
public:
    virtual void IamFunny()  = 0;
};

class FunnyImpl: IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};

class FunnyContainer
{
private:
    std::vector <IFunnyInterface> funnyItems;
};

บรรทัดที่ประกาศเวกเตอร์ของคลาสนามธรรมทำให้เกิดข้อผิดพลาดนี้ใน MS VS2005:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

ฉันเห็นวิธีแก้ปัญหาที่ชัดเจนซึ่งก็คือการแทนที่ IFunnyInterface ด้วยสิ่งต่อไปนี้:

class IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        throw new std::exception("not implemented");
    }
};

นี่เป็นวิธีแก้ปัญหาที่ยอมรับได้ C ++ หรือไม่? ถ้าไม่มีมีไลบรารีของบุคคลที่สามเช่นบูสต์ที่สามารถช่วยฉันแก้ปัญหานี้ได้หรือไม่?

ขอบคุณที่อ่านสิ่งนี้!

แอนโธนี่

คำตอบ:


128

คุณไม่สามารถสร้างอินสแตนซ์คลาสนามธรรมได้ดังนั้นเวกเตอร์ของคลาสนามธรรมจึงไม่สามารถทำงานได้

อย่างไรก็ตามคุณสามารถใช้เวกเตอร์ของพอยน์เตอร์กับคลาสนามธรรมได้:

std::vector<IFunnyInterface*> ifVec;

นอกจากนี้ยังช่วยให้คุณสามารถใช้งานจริงพฤติกรรม polymorphic - แม้ว่าระดับที่ไม่ได้เป็นนามธรรม, การจัดเก็บตามมูลค่าจะนำไปสู่ปัญหาของวัตถุหั่น


5
หรือคุณสามารถใช้ std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>> หากคุณไม่ต้องการจัดการกับอายุการใช้งานวัตถุด้วยตนเอง
Sergey Teplyakov

4
หรือดีกว่าให้เพิ่ม :: ptr_vector <>
Roel

7
หรือตอนนี้ std :: vector <std :: unique_ptr <IFunnyInterface>>
Kaz Dragon

21

คุณไม่สามารถสร้างเวกเตอร์ของประเภทคลาสนามธรรมได้เนื่องจากคุณไม่สามารถสร้างอินสแตนซ์ของคลาสนามธรรมและคอนเทนเนอร์ไลบรารีมาตรฐาน C ++ เช่น std :: vector store values ​​(เช่นอินสแตนซ์) หากต้องการทำเช่นนี้คุณจะต้องสร้างเวกเตอร์ของพอยน์เตอร์ไปยังประเภทคลาสนามธรรม

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

คุณควรตระหนักว่า C ++ และ C # มีอะไรที่เหมือนกันน้อยมาก หากคุณตั้งใจที่จะเรียนรู้ C ++ คุณควรคิดว่าเป็นการเริ่มต้นตั้งแต่ต้นและอ่านบทช่วยสอน C ++ เฉพาะที่ดีเช่นAccelerated C ++โดย Koenig และ Moo


ขอบคุณที่แนะนำหนังสือนอกเหนือจากการตอบโพสต์!
BlueTrin

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

6

ในกรณีนี้เราไม่สามารถใช้แม้แต่รหัสนี้:

std::vector <IFunnyInterface*> funnyItems;

หรือ

std::vector <std::tr1::shared_ptr<IFunnyInterface> > funnyItems;

เนื่องจากไม่มีความสัมพันธ์ระหว่าง FunnyImpl และ IFunnyInterface และไม่มีการแปลงโดยนัยระหว่าง FUnnyImpl และ IFunnyInterface เนื่องจากการสืบทอดส่วนตัว

คุณควรอัปเดตรหัสของคุณดังนี้:

class IFunnyInterface
{
public:
    virtual void IamFunny()  = 0;
};

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};

1
คนส่วนใหญ่มองข้ามมรดกส่วนตัวฉันคิดว่า :) แต่อย่าสับสนกับ OP มากไปกว่านี้ :)
Roel

1
ใช่. โดยเฉพาะอย่างยิ่งหลังจากวลีของผู้เริ่มต้นหัวข้อ: "ต้องใช้เวลาพอสมควรในการพัฒนา C #" (ซึ่งไม่มีการสืบทอดส่วนตัวเลย)
Sergey Teplyakov

6

ทางเลือกดั้งเดิมคือการใช้พvectorอยน์เตอร์ตามที่ระบุไว้แล้ว

สำหรับผู้ที่ชื่นชอบBoostมาพร้อมกับไลบรารีที่น่าสนใจมากPointer Containersซึ่งเหมาะอย่างยิ่งสำหรับงานและช่วยให้คุณเป็นอิสระจากปัญหาต่างๆที่บ่งชี้โดยคำแนะนำ

  • การจัดการอายุการใช้งาน
  • การอ้างอิงซ้ำสองครั้งของตัววนซ้ำ

โปรดทราบว่าสิ่งนี้ดีกว่าvectorตัวชี้อัจฉริยะอย่างมากทั้งในแง่ของประสิทธิภาพและอินเทอร์เฟซ

ตอนนี้มีทางเลือกที่ 3 ซึ่งก็คือการเปลี่ยนลำดับชั้นของคุณ เพื่อความเป็นฉนวนที่ดีขึ้นของผู้ใช้ฉันได้เห็นรูปแบบต่อไปนี้หลายครั้งที่ใช้:

class IClass;

class MyClass
{
public:
  typedef enum { Var1, Var2 } Type;

  explicit MyClass(Type type);

  int foo();
  int bar();

private:
  IClass* m_impl;
};

struct IClass
{
  virtual ~IClass();

  virtual int foo();
  virtual int bar();
};

class MyClass1: public IClass { .. };
class MyClass2: public IClass { .. };

นี่ค่อนข้างตรงไปตรงมาและรูปแบบของPimplสำนวนที่เสริมแต่งด้วยStrategyรูปแบบ

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


1
ขอบคุณสำหรับข้อมูลอ้างอิง Boost และรูปแบบการออกแบบ
BlueTrin

2

เนื่องจากในการปรับขนาดเวกเตอร์คุณจำเป็นต้องใช้ตัวสร้างเริ่มต้นและขนาดของคลาสซึ่งจะต้องให้เป็นรูปธรรม

คุณสามารถใช้ตัวชี้ตามคำแนะนำอื่น ๆ


1

std :: vector จะพยายามจัดสรรหน่วยความจำให้มีประเภทของคุณ ถ้าคลาสของคุณเป็นแบบเสมือนจริงเวกเตอร์ไม่สามารถทราบขนาดของคลาสที่จะต้องจัดสรร

ฉันคิดว่าด้วยวิธีแก้ปัญหาของคุณคุณจะสามารถรวบรวม a ได้vector<IFunnyInterface>แต่คุณจะไม่สามารถจัดการ FunnyImpl ภายในมันได้ ตัวอย่างเช่นถ้า IFunnyInterface (คลาสนามธรรม) มีขนาด 20 (ฉันไม่รู้จริงๆ) และ FunnyImpl มีขนาด 30 เนื่องจากมีสมาชิกและโค้ดมากกว่าคุณจะพยายามใส่ 30 ลงในเวกเตอร์ของคุณที่ 20

วิธีแก้ปัญหาคือจัดสรรหน่วยความจำบนฮีปด้วย "ใหม่" และจัดเก็บพอยน์เตอร์ใน vector<IFunnyInterface*>


ฉันคิดว่านี่คือคำตอบ แต่มองหาการตอบกลับ gf และการแบ่งส่วนวัตถุมันอธิบายว่าจะเกิดอะไรขึ้นภายในคอนเทนเนอร์
BlueTrin

คำตอบนี้อธิบายสิ่งที่จะเกิดขึ้น แต่ไม่ใช้คำว่า 'แบ่งส่วน' ดังนั้นคำตอบนี้จึงถูกต้อง เมื่อใช้เวกเตอร์ของ ptrs จะไม่มีการแบ่งส่วน นั่นคือจุดรวมของการใช้ ptrs ตั้งแต่แรก
Roel

-3

ฉันคิดว่าต้นตอของข้อ จำกัด ที่น่าเศร้าจริงๆนี้คือความจริงที่ว่าตัวสร้างไม่สามารถเสมือนจริงได้ คอมไพเลอร์ดังกล่าวไม่สามารถสร้างโค้ดที่คัดลอกวัตถุโดยไม่ทราบเวลาในเวลาคอมไพล์


2
นี่ไม่ใช่สาเหตุที่แท้จริงและไม่ใช่ "ข้อ จำกัด ที่น่าเศร้า"

โปรดอธิบายว่าเหตุใดคุณจึงคิดว่าไม่ใช่ข้อ จำกัด คงจะดีไม่น้อยหากมีความสามารถ และมีค่าใช้จ่ายบางอย่างสำหรับโปรแกรมเมอร์เมื่อเขา / เธอถูกบังคับให้ใส่พอยน์เตอร์ลงในคอนเทนเนอร์และกังวลเกี่ยวกับการลบ ฉันยอมรับว่าการมีวัตถุที่มีขนาดต่างกันในคอนเทนเนอร์เดียวกันจะทำให้ประสิทธิภาพการทำงานลดลง
David Gruzman

ฟังก์ชันเสมือนจัดส่งตามประเภทของวัตถุที่คุณมี จุดทั้งหมดของการก่อสร้างเป็นที่ไม่ได้มีวัตถุเลย เกี่ยวข้องกับสาเหตุที่คุณไม่สามารถมีฟังก์ชันเสมือนแบบคงที่: ไม่มีวัตถุด้วย
MSalters

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