Neden bir std :: vector <AbstractClass> bildiremiyoruz?


89

C # 'da geliştirmek için epey bir zaman harcadıktan sonra, onu bir arayüz olarak kullanmak amacıyla soyut bir sınıf bildirirseniz, çocuk sınıflarının örneklerini saklamak için bu soyut sınıfın bir vektörünü başlatamayacağınızı fark ettim.

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

Soyut sınıfın vektörünü bildiren satır MS VS2005'te bu hataya neden olur:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

IFunnyInterface'i aşağıdakilerle değiştiren bariz bir çözüm görüyorum:

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

Bu kabul edilebilir bir geçici çözüm C ++ akıllıca mı? Değilse, bu sorunu aşmama yardımcı olabilecek boost gibi herhangi bir üçüncü taraf kitaplığı var mı?

Bunu okuduğunuz için teşekkürler!

Anthony

Yanıtlar:


128

Soyut sınıfları başlatamazsınız, bu nedenle soyut sınıfların bir vektörü çalışamaz.

Bununla birlikte, sınıfları soyutlamak için bir işaretçi vektörü kullanabilirsiniz:

std::vector<IFunnyInterface*> ifVec;

Bu aynı zamanda polimorfik davranışı gerçekten kullanmanıza da olanak tanır - sınıf soyut olmasa bile, değere göre depolama, nesne dilimleme sorununa yol açar .


5
veya nesne yaşam süresiyle manuel olarak ilgilenmek istemiyorsanız std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>> kullanabilirsiniz.
Sergey Teplyakov

4
Daha da iyisi, boost :: ptr_vector <>.
Roel

7
Ya da şimdi std :: vector <std :: unique_ptr <IFunnyInterface>>.
Kaz Dragon

21

Soyut sınıf türünün bir vektörünü oluşturamazsınız çünkü soyut bir sınıfın örneklerini ve std :: vektör saklama değerleri (örn. Örnekler) gibi C ++ Standart Kitaplık kapsayıcılarını oluşturamazsınız. Bunu yapmak istiyorsanız, soyut sınıf türüne yönelik bir işaretçi vektörü oluşturmanız gerekecektir.

Çalışma çevreniz çalışmaz çünkü sanal işlevler (bu nedenle ilk etapta soyut sınıfı istiyorsunuz) yalnızca işaretçiler veya referanslar aracılığıyla çağrıldığında çalışır. Referans vektörleri de oluşturamazsınız, bu nedenle bu, bir işaretçi vektörü kullanmanız gerektiğinin ikinci nedenidir.

C ++ ve C # 'nin çok az ortak yönünün olduğunu anlamalısınız. C ++ öğrenmek istiyorsanız, sıfırdan başlayarak düşünmelisiniz ve Koenig ve Moo tarafından hazırlanan Accelerated C ++ gibi iyi bir özel C ++ öğreticisini okuyun .


Gönderiye yanıt vermenin yanı sıra bir kitap önerdiğiniz için teşekkür ederiz!
BlueTrin

Fakat soyut sınıfların bir vektörünü ilan ettiğinizde, ondan herhangi bir Soyut sınıf yaratmasını istemiyorsunuz, sadece bu sınıfın soyut olmayan bir alt sınıfını tutabilen bir vektör mü? Vektör yapıcısına bir sayı vermedikçe, soyut sınıfın kaç tane örneğinin yaratılacağını nasıl bilebilir?
Jonathan.

6

Bu durumda bu kodu bile kullanamayız:

std::vector <IFunnyInterface*> funnyItems;

veya

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

Çünkü FunnyImpl ve IFunnyInterface arasında IS A ilişkisi yoktur ve FUnnyImpl ile IFunnyInterface arasında özel kalıtım nedeniyle örtük bir dönüşüm yoktur.

Kodunuzu aşağıdaki şekilde güncellemelisiniz:

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

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

1
Çoğu kişi özel mirasa baktı sanırım :) Ama
OP'yi

1
Evet. Özellikle konuyu başlatan ifadeden sonra: "C # 'da geliştirmek için epey zaman harcadım" (özel mirasın olmadığı yerde).
Sergey Teplyakov

6

Geleneksel alternatif, daha vectorönce belirtildiği gibi, bir işaretçi kullanmaktır .

Takdir edenler için, Boostçok ilginç bir kitaplıkla birlikte gelir: Pointer Containersgörev için mükemmel şekilde uygundur ve sizi işaretçilerin ima ettiği çeşitli sorunlardan kurtarır:

  • ömür boyu yönetim
  • yineleyicilerde çift başvurunun kaldırılması

Bunun vectorhem performans hem de arayüz açısından akıllı işaretçilerden önemli ölçüde daha iyi olduğunu unutmayın .

Şimdi, hiyerarşinizi değiştirmek için 3. bir alternatif var. Kullanıcının daha iyi yalıtımı için, aşağıdaki modelin birkaç kez kullanıldığını gördüm:

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

Bu oldukça basittir ve bir PimpldeyiminStrategy kalıpla .

Elbette, yalnızca "gerçek" nesneleri doğrudan manipüle etmek istemediğinizde çalışır ve derin kopyayı içerir. Yani dilediğin gibi olmayabilir.


1
Boost referansı ve tasarım deseni için teşekkür ederiz
BlueTrin

2

Çünkü bir vektörü yeniden boyutlandırmak için varsayılan kurucuyu ve sınıfın boyutunu kullanmanız gerekir, bu da onun somut olmasını gerektirir.

Diğer önerildiği gibi bir işaretçi kullanabilirsiniz.


1

std :: vector, türünüzü içerecek bellek ayırmaya çalışacaktır. Sınıfınız tamamen sanalsa, vektör ayırmak zorunda kalacağı sınıfın boyutunu bilemez.

Sanırım geçici çözümünüzle, bir derleme yapabileceksiniz, vector<IFunnyInterface>ancak içinde FunnyImpl'i kullanamayacaksınız. Örneğin, IFunnyInterface (soyut sınıf) boyutu 20 ise (gerçekten bilmiyorum) ve FunnyImpl boyutu 30 ise, çünkü daha fazla üye ve kod içeriyorsa, 20'lik vektörünüze 30'u sığdırmaya çalışacaksınız.

Çözüm, "yeni" ile yığın üzerinde bellek ayırmak ve işaretçileri depolamak olacaktır. vector<IFunnyInterface*>


Bu cevap olduğunu düşünmüştüm, ama, bu kabın içinde gerçekleşecektir tam olarak ne anlatmak gf cevapta ve nesne dilimleme için bakmak
BlueTrin

Bu cevap, 'dilimleme' kelimesini kullanmadan ne olacağını açıkladı, bu yüzden bu cevap doğrudur. Bir ptrs vektörü kullanıldığında dilimleme yapılmaz. İlk etapta ptr kullanmanın amacı budur.
Roel

-3

Bence bu gerçekten üzücü bir sınırlamanın temel nedeni, kurucuların sanal yapamamasıdır. Bu nedenle derleyici, derleme zamanındaki zamanını bilmeden nesneyi kopyalayan bir kod üretemez.


2
Temel neden bu değildir ve "üzücü bir sınırlama" değildir.

Lütfen bunun bir sınırlama olmadığını düşündüğünüzü açıklayın. Yeteneğe sahip olmak güzel olurdu. Ve programcı, konteynere işaretçiler koymak zorunda kaldığında ve silme konusunda endişelendiğinde bazı ek yükler vardır. Aynı kapta farklı boyutlarda nesnelere sahip olmanın performansı düşürdüğüne katılıyorum.
David Gruzman

Sanal işlevler, sahip olduğunuz nesnenin türüne göre gönderilir. Yapıcıların tüm noktası, henüz bir nesneye sahip olmamasıdır . Statik sanal işlevlere sahip olamamanın nedeniyle ilgili: ayrıca nesne yok.
MSalters

Sınıf kapsayıcı şablonunun nesneye ihtiyacı olmadığını söyleyebilirim, bir sınıf fabrikası ve yapıcı bunun doğal bir parçası.
David Gruzman
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.