Mengapa kita tidak bisa mendeklarasikan std :: vector <AbstractClass>?

89

Setelah menghabiskan cukup banyak waktu untuk mengembangkan C #, saya perhatikan bahwa jika Anda mendeklarasikan kelas abstrak untuk tujuan menggunakannya sebagai antarmuka, Anda tidak dapat membuat contoh vektor kelas abstrak ini untuk menyimpan instance kelas anak.

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

Baris yang mendeklarasikan vektor kelas abstrak menyebabkan kesalahan ini di MS VS2005:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

Saya melihat solusi yang jelas, yaitu mengganti IFunnyInterface dengan yang berikut:

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

Apakah ini solusi yang dapat diterima C ++ bijaksana? Jika tidak, apakah ada pustaka pihak ketiga seperti boost yang dapat membantu saya mengatasi masalah ini?

Terima kasih telah membaca ini!

Anthony

BlueTrin
sumber

Jawaban:

128

Anda tidak dapat membuat instance kelas abstrak, sehingga vektor kelas abstrak tidak dapat berfungsi.

Namun Anda dapat menggunakan vektor pointer ke kelas abstrak:

std::vector<IFunnyInterface*> ifVec;

Ini juga memungkinkan Anda untuk benar-benar menggunakan perilaku polimorfik - bahkan jika kelasnya tidak abstrak, menyimpan berdasarkan nilai akan menyebabkan masalah pemotongan objek .

Georg Fritzsche
sumber
5
atau Anda dapat menggunakan std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>> jika Anda tidak ingin berurusan dengan umur objek secara manual.
Sergey Teplyakov
4
Atau bahkan lebih baik, tingkatkan :: ptr_vector <>.
Roel
7
Atau sekarang, std :: vector <std :: unique_ptr <IFunnyInterface>>.
Kaz Dragon
21

Anda tidak dapat membuat vektor dari jenis kelas abstrak karena Anda tidak dapat membuat instance dari kelas abstrak, dan wadah C ++ Standard Library seperti std :: vector store values ​​(yaitu instance). Jika Anda ingin melakukan ini, Anda harus membuat vektor pointer ke tipe kelas abstrak.

Lingkungan kerja Anda tidak akan berfungsi karena fungsi virtual (itulah sebabnya Anda menginginkan kelas abstrak di tempat pertama) hanya berfungsi ketika dipanggil melalui pointer atau referensi. Anda juga tidak dapat membuat vektor referensi, jadi ini adalah alasan kedua mengapa Anda harus menggunakan vektor pointer.

Anda harus menyadari bahwa C ++ dan C # memiliki sedikit kesamaan. Jika Anda berniat untuk mempelajari C ++, Anda harus memikirkannya dari awal, dan membaca tutorial C ++ khusus yang bagus seperti Accelerated C ++ oleh Koenig dan Moo.


sumber
Terima kasih telah merekomendasikan buku selain membalas postingan!
BlueTrin
Tetapi ketika Anda mendeklarasikan vektor kelas abstrak, Anda tidak memintanya untuk membuat kelas Abstrak, hanya vektor yang mampu menampung subkelas non abstrak dari kelas itu? Kecuali jika Anda meneruskan angka ke konstruktor vektor, bagaimana mungkin ia mengetahui berapa banyak instance dari kelas abstrak yang akan dibuat?
Jonathan.
6

Dalam hal ini kami tidak dapat menggunakan bahkan kode ini:

std::vector <IFunnyInterface*> funnyItems;

atau

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

Karena tidak ada hubungan IS A antara FunnyImpl dan IFunnyInterface dan tidak ada konversi implisit antara FUnnyImpl dan IFunnyInterface karena warisan pribadi.

Anda harus memperbarui kode Anda sebagai berikut:

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

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};
Sergey Teplyakov
sumber
1
Kebanyakan orang melihat-lihat warisan pribadi saya pikir :) Tapi jangan bingung lagi dengan OP :)
Roel
1
Ya. Terutama setelah frase pembuka topik: "Setelah menghabiskan cukup banyak waktu mengembangkan di C #" (di mana tidak ada warisan pribadi sama sekali).
Sergey Teplyakov
6

Alternatif tradisional adalah dengan menggunakan salah vectorsatu petunjuk, seperti yang telah disebutkan.

Bagi mereka yang menghargai, Boosthadir dengan perpustakaan Pointer Containersyang sangat menarik: yang sangat cocok untuk tugas tersebut dan membebaskan Anda dari berbagai masalah yang tersirat oleh petunjuk:

  • manajemen seumur hidup
  • dereferensi ganda dari iterator

Perhatikan bahwa ini jauh lebih baik daripada vectorsmart pointer, baik dalam hal kinerja dan antarmuka.

Sekarang, ada alternatif ketiga, yaitu mengubah hierarki Anda. Untuk isolasi pengguna yang lebih baik, saya telah melihat beberapa kali pola berikut digunakan:

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

Ini cukup jelas, dan variasi Pimplidiom diperkaya oleh sebuah Strategypola.

Ia bekerja, tentu saja, hanya dalam kasus di mana Anda tidak ingin memanipulasi objek "sebenarnya" secara langsung, dan melibatkan deep-copy. Jadi itu mungkin bukan yang Anda inginkan.

Matthieu M.
sumber
1
Terima kasih atas referensi Boost dan pola desainnya
BlueTrin
2

Karena untuk mengubah ukuran vektor Anda perlu menggunakan konstruktor default dan ukuran kelas, yang pada gilirannya mengharuskannya menjadi konkret.

Anda dapat menggunakan penunjuk seperti yang disarankan lainnya.

kennytm
sumber
1

std :: vector akan mencoba mengalokasikan memori untuk menampung tipe Anda. Jika kelas Anda murni virtual, vektor tidak dapat mengetahui ukuran kelas yang harus dialokasikan.

Saya pikir dengan solusi Anda, Anda akan dapat mengkompilasi vector<IFunnyInterface>tetapi Anda tidak akan dapat memanipulasi FunnyImpl di dalamnya. Misalnya jika IFunnyInterface (kelas abstrak) berukuran 20 (saya tidak begitu tahu) dan FunnyImpl berukuran 30 karena memiliki lebih banyak anggota dan kode, Anda akan mencoba memasukkan 30 ke dalam vektor 20 Anda

Solusinya adalah dengan mengalokasikan memori di heap dengan "baru" dan menyimpan petunjuk di dalamnya vector<IFunnyInterface*>

Eric
sumber
Saya pikir ini adalah jawabannya, tetapi cari jawaban gf dan pemotongan objek, itu menjelaskan dengan tepat apa yang akan terjadi dalam wadah
BlueTrin
Jawaban ini menggambarkan apa yang akan terjadi tanpa menggunakan kata 'mengiris', jadi jawaban ini benar. Saat menggunakan vektor ptrs, tidak ada pemotongan yang akan terjadi. Itulah inti dari menggunakan ptrs di tempat pertama.
Roel
-3

Saya pikir akar penyebab dari batasan yang sangat menyedihkan ini adalah kenyataan bahwa konstruktor tidak dapat virtual. Daripadanya penyusun tidak dapat menghasilkan kode yang menyalin objek tanpa mengetahui waktunya pada waktu kompilasi.

David Gruzman
sumber
2
Ini bukan akar penyebabnya, dan ini bukan "batasan yang menyedihkan".
Tolong jelaskan mengapa menurut Anda itu bukan batasan? Alangkah baiknya memiliki kemampuan. Dan ada beberapa overhead pada programmer ketika dia dipaksa untuk meletakkan pointer ke container, dan khawatir tentang penghapusan. Saya setuju bahwa memiliki objek dengan ukuran berbeda dalam wadah yang sama akan mengganggu kinerja.
David Gruzman
Pengiriman fungsi virtual berdasarkan jenis objek yang Anda miliki. Seluruh titik konstruktor adalah bahwa tidak memiliki objek belum . Terkait dengan alasan mengapa Anda tidak dapat memiliki fungsi virtual statis: juga tidak ada objek.
MSalters
Saya dapat mengatakan bahwa templat penampung kelas tidak perlu objek, tetapi pabrik kelas, dan konstruktor adalah bagian alami darinya.
David Gruzman