如何正确实现对象列表的创建(和分配)仅由C++中的抽象基知道

How to properly implement creation (and allocation) of a list of objects known only by abstract base in C++?

本文关键字:仅由 C++ 抽象 对象 实现 何正确 列表 创建 分配      更新时间:2023-10-16

我应该更喜欢堆栈分配而不是堆分配。最好按值传递(特别是当你正在创建新对象时 - 但同时,如果你按基类返回,你的对象将被切片),或者至少通过引用传递指针(尽管你不能创建引用向量)。

我仔细阅读了所有这些,现在我觉得我知道的比以前知道的少了。我对如何编写一个 IMO 应该微不足道的代码一无所知,同时尊重所有这些精心编写和深思熟虑的答案中提到的最佳实践。

这就是我想要实现的内容。(我不假装它是正确的C++,但我只是想传达这个想法)。

// This thing is purely virtual!
class BaseStuff { }
// Has important behaviour and data members that shouldn't be sliced
class SomeStuff : public BaseStuff { }
// Don't slice me plz
class OtherStuff : public BaseStuff { }
BaseStuff CreateStuff()
{
    // falls a set of rules to create SomeStuff or OtherStuff instance based on phase of the moon
}
std::vector<BaseStuff> CreateListOfStuff()
{
    // calls CreateStuff a lot
}
public static void main()
{
    List<BaseStuff> allTheStuff = CreateListOfStuff();
    // to things with stuff
}

如果你想要多态性,你必须使用指针。

BaseStuff* CreateStuff()
{
   ...
}
List<BaseStuff*> allTheStuff = CreateListOfStuff();

实际上,如果可能,您应该更喜欢堆栈分配而不是堆分配。但这并不总是可能的。

当然,您可以考虑使用智能指针 - unique_ptr、shared_ptr等,而不是本质上不安全的原始指针,但智能指针仍然

List<shared_ptr<BaseStuff>> = CreateListOfStuff

您将存储指向基类的多态指针,该基类实际上指向派生类型(注意我在这里使用了std::unique_ptr s):

#include <iostream>
#include <memory>
#include <vector>
class Base
{
public:
  virtual void shout() = 0;
};
class Child : public Base
{
public:
  void shout() { std::cout << "Child!n"; }
};
class Orphan : public Base
{
public:
  void shout() { std::cout << "Orphan!n"; }
};
int main()
{
  std::vector<std::unique_ptr<Base>> the_list;
  the_list.reserve(3);
  the_list.emplace_back(std::make_unique<Child>());
  the_list.emplace_back(std::make_unique<Orphan>());
  the_list.emplace_back(std::make_unique<Child>());
  for(const auto& item : the_list)
  {
    item->shout(); // remember, items are (smart) pointers!
  }
}

活生生的例子。在标准C++容器中只能存储一个类型,因此必须在此处存储指向 Base 的(智能)指针。

在这种情况下,您经常(通常?)做的一件事是在基类中定义一个成员函数(传统上称为 clone )以创建对象的副本:

class BaseStuff { 
public:
    virtual BaseStuff *clone() const = 0;
};
class SomeStuff : public BaseStuff {     
    virtual void SomeStuff *clone() const { 
        return new SomeStuff(*this); // needs a correctly functioning copy ctor
    }
};
class OtherStuff : public BaseStuff {
public: 
    virtual OtherStuff *clone() const { 
        return new OtherStuff(*this);
    }
};

这使我们能够通过指向基的指针复制项目,而无需切片。这样,如果您希望(例如)您的ListOfStuff存储您放入其中的项目的副本(而不仅仅是指向原始项目的指针,这些项目可能不复存在,留下悬空的指针),您可以添加如下项目:

class ListOfStuff { 
    class Node {
        // Note: this is extremely incomplete. Almost certainly needs a dtor,
        // and you want to either implement or delete the copy ctor to either
        // do copying correctly, or assure it can't happen at all.
        Node *next;
        BaseStuff *data;
    public:
        Node(BaseStuff *data, Node *next = nullptr) : data(data), next(next) {}
    };
    Node *begin, *end;
public:
    Add(BaseStuff *item) { 
        BaseStuff *new_item = item->clone();
        end->next = new node(new_item);
        end = end->next;
    }    
};

通过一个小的修改,我同意Armen Tsirunyan的基本观点:当你处理多态层次结构时,你通常必须通过指针或(修改)引用来做到这一点。

在许多情况下,您可以使用智能指针而不是指针 - 但并非在所有情况下,并且为特定情况选择正确的智能指针本身可能并非易事。不过,您通常确实希望在您的指针周围使用某种包装器 - 上面的代码避免使用它们,主要是因为我对您在做什么没有足够的了解来选择正确的指针。