将派生类对象存储在基类变量中

Store derived class objects in base class variables

本文关键字:基类 类变量 存储 派生 对象      更新时间:2023-10-16

我想在一个向量中存储几个类的实例。由于所有类都继承自同一个基类,因此这应该是可能的。

想象一下这个程序:

#include <iostream>
#include <vector>
using namespace std;
class Base
{
    public:
    virtual void identify ()
    {
        cout << "BASE" << endl;
    }
};
class Derived: public Base
{
    public:
    virtual void identify ()
    {
        cout << "DERIVED" << endl;
    }
};
int main ()
{
    Derived derived;
    
    vector<Base> vect;
    vect.push_back(derived);
    
    vect[0].identify();
    return 0;
}

我希望它打印"DERIVED",因为identify()方法是virtual。相反,vect[0]似乎是Base实例,并且它打印"BASE"

我想我可以写我自己的容器(可能源自vector),以某种方式能够做到这一点(可能只包含指针…)

我只是想问一下,是否有一种更像C++的方法可以做到这一点。而且我希望完全兼容vector(只是为了方便其他用户使用我的代码)。

您看到的是对象切片
您将派生类的对象存储在一个向量中,该向量本应存储基类的对象,这导致对象切片,并且存储的对象的派生类特定成员被切片,因此存储在向量中的对象仅充当基类的对象。

解决方案:

您应该将指向基类对象的指针存储在向量中:

vector<Base*> 

通过存储一个指向基类的指针,就不会有切片,而且还可以实现所需的多态行为
由于您要求使用C++ish方法,因此正确的方法是使用合适的智能指针,而不是在矢量中存储原始指针。这将确保您不必手动管理内存,RAII将自动为您管理内存。

您正在经历切片。矢量复制derived对象,插入类型为Base的新对象。

TL;DR:你不应该从公开的可复制/可移动类继承


实际上,在编译时可以防止对象切片:基本对象在这个上下文中不应该是可复制的。

案例1:抽象基础

如果基是抽象的,那么它就无法实例化,因此您就无法体验切片。

案例2:混凝土基础

如果基础不是抽象的,那么它可以被复制(默认情况下)。你有两个选择:

  • 完全禁止复制
  • 只允许儿童复制

注意:在C++11中,移动操作也会导致同样的问题。

// C++ 03, prevent copy
class Base {
public:
private:
    Base(Base const&);
    void operator=(Base const&);
};
// C++ 03, allow copy only for children
class Base {
public:
protected:
    Base(Base const& other) { ... }
    Base& operator=(Base const& other) { ...; return *this; }
};
// C++ 11, prevent copy & move
class Base {
public:
    Base(Base&&) = delete;
    Base(Base const&) = delete;
    Base& operator=(Base) = delete;
};
// C++ 11, allow copy & move only for children
class Base {
public:
protected:
    Base(Base&&) = default;
    Base(Base const&) = default;
    Base& operator=(Base) = default;
};

我会使用vector<Base*>来存储它们。如果说vector<Base>,则会发生切片。

这确实意味着,在从向量中删除指针后,您必须自己删除实际对象,但除此之外,您应该没事。

// Below is the solution by using vector<Based*> vect,
// Base *pBase , and initialized pBase with
// with the address of derived which is
// of type Derived
#include <iostream>
#include <vector>
using namespace std;
class Base
{
public:
virtual void identify ()
{
    cout << "BASE" << endl;
}
};
class Derived: public Base
{
public:
virtual void identify ()
{
    cout << "DERIVED" << endl;
}
};
int main ()
{
Base *pBase; // The pointer pBase of type " pointer to Base"
Derived derived;
// PBase is initialized with the address of derived which is
// of type Derived
pBase = & derived;
// Store pointer to object of Base class in the vector:
vector<Base*> vect;
// Add an element to vect using pBase which is initialized with the address 
// of derived
vect.push_back(pBase);
vect[0]->identify();
return 0;
}

正如这里提到的所有其他对象一样,由于复制构造时会发生对象切片,您无法将派生的对象插入到的向量中。

如果目标是避免内存分配,您可以使用std::variant,但向量将不再是基本

using HierarchyItem = std::variant<Base, Derived>;
int main()
{
    vector<HierarchyItem> vect;
    vect.push_back(Derived());
    std::visit([](auto &&hier_item){ hier_item.identify(); }, vect[0]);
    return 0;
}