为什么我们不能声明std :: vector <AbstractClass>?


88

我花了很多时间在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 ++明智的解决方法吗?如果没有,是否有像boost这样的第三方库可以帮助我解决这个问题?

谢谢您阅读此篇 !

安东尼

Answers:


127

您不能实例化抽象类,因此抽象类的向量不能工作。

但是,您可以使用指向抽象类的指针向量:

std::vector<IFunnyInterface*> ifVec;

这也使您可以实际使用多态行为-即使该类不是抽象类,按值存储也会导致对象切片的问题。


5
或者,如果您不想手动处理对象生存期,则可以使用std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>>。
谢尔盖·特普里亚科夫

4
甚至更好的是boost :: ptr_vector <>。
Roel 2010年

7
或者现在,std :: vector <std :: unique_ptr <IFunnyInterface >>。
Kaz Dragon 2014年

21

您无法创建抽象类类型的向量,因为您无法创建抽象类的实例,并且诸如std :: vector之类的C ++标准库容器将存储值(即实例)。如果要执行此操作,则必须创建一个指向抽象类类型的指针向量。

您的工作流程将无法正常工作,因为虚函数(这就是为什么您首先要抽象类的原因)仅在通过指针或引用调用时才起作用。您也不能创建引用向量,因此这是您必须使用指针向量的第二个原因。

您应该意识到C ++和C#几乎没有共同之处。如果您打算学习C ++,则应从头开始,并阅读专门的C ++教程,例如Koenig和Moo的Accelerated C ++


感谢您除了回复帖子之外还推荐一本书!
BlueTrin 2010年

但是,当您声明一个抽象类的向量时,您不是要它创建任何Abstract类,而是一个能够容纳该类的非抽象子类的向量吗?除非您将数字传递给vectors构造函数,否则它将如何知道要创建多少个抽象类实例?
乔纳森。

6

在这种情况下,我们甚至不能使用以下代码:

std::vector <IFunnyInterface*> funnyItems;

要么

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

因为FunnyImpl和IFunnyInterface之间没有IS A关系,并且由于私有继承,FUnnyImpl和IFunnyInterface之间也没有隐式转换。

您应该按照以下方式更新代码:

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

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

1
我认为大多数人都看过私有继承:)但让我们不要进一步混淆OP :)
Roel 2010年

1
是的 尤其是在主题入门者所说的短语之后:“花了很多时间在C#中开发”(根本没有私有继承)。
谢尔盖·特普利亚科夫

6

传统的替代方法是使用vector指针a ,如前所述。

对于那些欣赏Boost它的人来说,它带有一个非常有趣的库:Pointer Containers该库非常适合该任务并使您摆脱指针所隐含的各种问题:

  • 终生管理
  • 迭代器的双重取消引用

请注意,vector就性能和接口而言,这比智能指针要好得多。

现在,有第三个选择,即更改您的层次结构。为了更好地隔离用户,我多次看到使用以下模式:

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 2010年

2

因为要调整向量的大小,您需要使用默认的构造函数和类的大小,这又需要使其具体。

您可以使用其他建议的指针。


1

std :: vector将尝试分配内存以包含您的类型。如果您的类是纯虚拟的,则向量无法知道它将必须分配的类的大小。

我认为通过您的变通办法,您将能够编译一个,vector<IFunnyInterface>但您将无法在其中操作FunnyImpl。例如,如果IFunnyInterface(抽象类)的大小为20(我不太清楚),而FunnyImpl的大小为30,因为它具有更多的成员和代码,您最终将尝试将30装入向量20中

解决方案是使用“ new”在堆上分配内存,并将指针存储在 vector<IFunnyInterface*>


我以为这就是答案,但是查找gf答复和对象切片,它确切地解释了容器内将发生的情况
BlueTrin 2010年

该答案描述了将会发生的情况,但未使用“切片”一词,因此此答案是正确的。使用ptrs向量时,将不会发生切片。这就是首先使用ptrs的全部意义。
Roel 2010年

-2

我认为,这种真正可悲的局限性的根本原因是构造函数无法虚拟化。因此,编译器无法在不知道编译时间的情况下生成用于复制对象的代码。


2
这不是根本原因,也不是“悲伤的限制”。

请解释为什么您认为这不是一个限制?有能力会很好。当程序员被迫将指针放到容器中并担心删除时,这会带来一些开销。我同意在同一容器中放置不同尺寸的物体会影响性能。
David Gruzman 2010年

虚拟函数根据您所拥有的对象的类型进行分派。构造的整个观点是,没有一个对象还没有。与不能使用静态虚拟函数的原因有关:也没有对象。
MSalters 2010年

我可以说类容器的模板不需要对象,而是类工厂,而构造函数是其自然的一部分。
David Gruzman 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.