【问题标题】:Abstract types Meet std Containers抽象类型符合标准容器
【发布时间】:2016-07-29 08:20:16
【问题描述】:

我一直在尝试解决一个涉及抽象类型及其派生类以及标准容器的设计问题。我有一个可行的解决方案,但我想看看它是否可以改进。我为“……”道歉。但它是为了让设计问题更加清晰。

// 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 的某些实例,而无需知道它们的具体类型。目前,我正在使用 point to 方法,这意味着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 + 原始指针容器更好的方法,请告诉我。

【问题讨论】:

  • 为什么要“摆脱原始指针”?使用非拥有的原始指针(例如,see this answer)非常好。
  • @Serikov,通过“我正在尝试摆脱原始指针”,它实际上意味着“我的同事坚持摆脱原始指针”...... :(
  • 您的同事可能对对象生命周期与对象的分离感到不舒服?如果您需要将结果结构传递到某个地方,您需要确保对象没有过期。如果是这种情况,您可以将此结构包装在对象(例如,Graph)中,以控制其节点的生命周期。无论哪种方式,当创建对象的生命周期是从外部控制时(通过vector 或其他方式),您可以在构造函数中使用引用而不是指针。
  • @Serikov。假设对象由向量控制,并且我们使用引用而不是指针,您能详细说明一下吗?在这种特殊情况下,我的理解是B 的对象不能包含 在向量中,因为B 是一种抽象类型。有没有办法解决这个问题?
  • 如果您像现在一样将unique_ptrs 存储在您的vector 中,当另一个对象被推入向量时,指向unique_ptrs 的指针(或引用)将失效,但指针(和引用)通过 unique_ptr 存储的对象不会。因此,您可以使用对它们的引用。您可以使用 D1(*unique, *unique) 而不是使用 D1(unique1.get(), unique2.get()) (并更改该构造函数以获取引用)。

标签: c++ oop c++11


【解决方案1】:

使用非拥有的原始指针完全没问题(例如,参见this answer)。
如果您的目标是在构造函数和派生类的成员中使用引用而不是指针,则无需对设计进行重大更改即可做到这一点。
例如,

#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 << "D1\n";
        b1.func();
        b2.func();
    }
};

class D2 : public B {
    B &b;
public:
    D2(B &b) : b(b) { }
    void func() {
        std::cout << "D2\n";
        b.func();
    }
};

class D3 : public B {
public:
    D3() { }
    void func() {
        std::cout << "D3\n";
    }
};

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();
}

【讨论】:

    猜你喜欢
    • 2021-12-18
    • 2011-06-15
    • 2022-01-09
    • 2014-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-30
    • 2019-09-05
    相关资源
    最近更新 更多