【发布时间】: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(...) {...}
};
基于该问题,D1 和D2 都必须指向 或包含 B 的某些实例,而无需知道它们的具体类型。目前,我正在使用 point to 方法,这意味着D1 和D2 包含一些指向B 的指针。
此外,B 的那些指向实例实际上在递归组合中在 B 的更高级别实例之间共享。为了清楚起见,我将扩展D1的定义。
class D1 : public B {
private:
B *d1_b1;
B *d1_b2;
...
public:
D1 (B *b1, B *b2) {...}
B* func(...) {...}
...
};
现在,尽管d1_b1 和d1_b2 由D1 或D2 的一些更高级别的实例共享,但我认为任何更高级别的对象都不应该维护更低级别的所有权或生命周期级对象。相反,我使用带有unique_ptrs 的 std 容器,例如:
std::vector<std::unique_ptr<B>> unique_b_objs;
unique_b_objs 在第一次调用递归构造的范围内声明,并通过引用传入并递归使用。
现在,每次创建D1 或D2 的新对象时,都会将相应的unique_ptr 推回unique_b_objs。本质上,指向 B 对象的原始指针 用于递归组合,但B 对象的生命周期和所有权由 std 容器管理。
我希望到目前为止我已经说得很清楚了。它按预期工作。但我正在尝试摆脱原始指针。我可以想到两种方法,但现在都没有。
-
使用“包含”方法。本质上,所有
B *在D1和D2中都变成了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()) (并更改该构造函数以获取引用)。