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

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

想象一下这个程序:

#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兼容(只是为了方便,如果其他用户曾经使用我的代码)。

56578 次浏览

你看到的是 对象切片
你把派生类的对象存储在一个向量中,这个向量应该存储基类的对象,这会导致对象切片和被存储对象的派生类特定成员被切除,因此向量中存储的对象只是作为基类的对象。

解决方案:

应该在向量中存储指向 Base 类对象的指针:

vector<Base*>

通过存储指向 Base 类的指针,就不需要进行切片,而且还可以实现所需的多态行为。
因为您要求使用 C++ish方法来完成这项工作,所以正确的方法是使用合适的 智能指针,而不是在向量中存储原始指针。这将确保您不必手动管理的内存,资源获取初始化将为您自动做到这一点。

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

这意味着在从向量中移除指针之后,必须自己删除实际的对象,否则应该没问题。

你正在经历切片。向量复制 derived对象,插入一个类型为 Base的新对象。

DR: 不应该从公共可复制/可移动类继承。


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

案例1: 抽象基础

如果基础是抽象的,那么它就不能被实例化,因此您就不能体验切片。

案例2: 混凝土基础

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

  • 完全禁止复制
  • 只准儿童复制

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

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