抽象类型符合标准容器

Abstract types Meet std Containers

本文关键字:标准 抽象类 类型 抽象      更新时间:2023-10-16

我一直在努力解决一个涉及抽象类型及其派生类以及std容器的设计问题。我有一个可行的解决方案,但我想看看是否可以改进。我为"…"道歉。但这是为了让设计问题更清楚。

// the base class
class B { 
private:
    ...
public:
    // the virtual function which makes B an abstract type
    virtual void func(...) = 0; 
    ...
};
// the 1st derived class
class D1 : public B { 
    ...
    void func(...) {...}
};
// the 2nd derived class
class D2 : public B {
    ...
    void func(...) {...}
};

基于这个问题,D1D2都必须指向包含B的一些实例,而不知道它们的具体类型。目前,我使用点到的方法,这意味着D1D2包含一些指向B的指针。

此外,那些指向B实例的实例实际上是在递归组合中B的高级实例之间共享的。为了明确起见,我将扩展D1的定义。

class D1 : public B { 
private:
    B *d1_b1;
    B *d1_b2;
    ...
public:
    D1 (B *b1, B *b2) {...}
    B* func(...) {...}
    ...
};

现在,尽管d1_b1d1_b2D1D2的一些高级实例共享,但我认为任何高级对象都不应保持较低级别对象的所有权或生存期。相反,我使用了一个带有unique_ptrs的std容器,例如:

std::vector<std::unique_ptr<B>> unique_b_objs;

unique_b_objs在首次调用递归构造的作用域中声明,并通过引用传入并递归使用。

现在,每次创建D1D2的新对象时,都会将相应的unique_ptr推回到unique_b_objs中。本质上,指向B对象的原始指针用于递归组合,但B对象的生存期和所有权由std容器管理。

我希望到目前为止我已经说清楚了。它正在按预期工作。但我正在尝试去掉原始指针。我能想到两种方法,但现在都不起作用。

  • 使用"包含"方法。本质上,所有的B *D1D2中变为B。但B是一种抽象类型,这意味着编译器甚至不会编译类似D1 (B b1, B b2) {...}的东西
  • 在容器中使用引用。这可以通过reference_wrapper来完成。但是,由于对象是在递归中本地创建的,因此引用在作用域之外将变得无效。这意味着我们不能在递归调用之间传输引用

一种工作方法是使用shared_ptr。但是没有必要在每个递归步骤共享所有权。这也会导致性能开销。

在这一点上,感谢您通读这个问题。我希望我已经说清楚了。如果你能想出比unique_ptrs+原始指针的容器更好的方法,请告诉我。

使用非拥有的原始指针是非常好的(例如,请参阅此答案)
如果您的目标是在构造函数和派生类的成员中使用引用而不是指针,那么您可以在不对设计进行重大更改的情况下做到这一点
例如,

#include <iostream>
#include <vector>
#include <memory>
class B {
private:
public:
    virtual void func() = 0;
};
std::vector<std::unique_ptr<B>> arena;
class D1 : public B {
    B &b1;
    B &b2;
public:
    D1(B &b1, B &b2) : b1(b1), b2(b2) { }
    void func() {
        std::cout << "D1n";
        b1.func();
        b2.func();
    }
};
class D2 : public B {
    B &b;
public:
    D2(B &b) : b(b) { }
    void func() {
        std::cout << "D2n";
        b.func();
    }
};
class D3 : public B {
public:
    D3() { }
    void func() {
        std::cout << "D3n";
    }
};
int main() {
    auto node = std::make_unique<D3>(); // make new object
    auto& nodeRef = *node; // get reference to the object
    arena.push_back(std::move(node)); // transfer ownership to the vector,
                                      // nodeRef is not invalidated
    auto node1 = std::make_unique<D2>(nodeRef); // use reference
    auto& node1Ref = *node1;
    arena.push_back(std::move(node1));
    auto node2 = std::make_unique<D3>();
    auto& node2Ref = *node2;
    arena.push_back(std::move(node2));
    auto node3 = std::make_unique<D1>(node1Ref, node2Ref);
    arena.push_back(std::move(node2));
    node3->func();
}