请考虑以下代码:
class Base {
protected:
int* ptr_;
public:
virtual ~Base() { /* delete ptr_ ?? */ }
}
class A : public Base {
public:
A() { ptr_ = new int; }
~A() { /* delete ptr_ ?? */ }
}
Run Code Online (Sandbox Code Playgroud)
我和我的同事发生了轻微的争执.
哪个析构函数应该删除ptr_?
我认为它应该在A,因为它是分配的地方.
他认为它应该存在Base,因为那是指针所在的位置.
请告诉我为什么一种方法比另一种方法更好(如果有一种方法,这不仅仅是品味问题)
编辑
很多人质疑设计.在实践中,代码要复杂得多,并且模拟了涉及几个相机的图像处理系统.Base的作用是管理系统的资源(存在一些配置).类A和其他派生类型决定Base了系统的配置并将系统初始化为特定配置.我想现在你可以问一下,如果继承是合成的正确选择.Ais-a Base,只是一种特定的类型,这就是我们选择继承的原因.在这种情况下,ptr_是一个指向摄像机特定驱动程序的指针,它将由派生类型决定,这就是它在那里分配的原因.
每个班级都应该管理自己的资源.当你有多个派生类时,每个必须记住释放指针.这很糟糕,因为它复制了代码.
如果允许某些派生类不为指针指定新值,则在基类构造函数中将其设置为零.如果应该允许某些派生类将指针分配给自动变量的地址,那么请检查您的设计.
使用智能指针使问题完全消失(当我grep delete *在我的整个代码库中,我发现没有匹配:)
问题是设计不止一个实现.我倾向于同意你的同事,如果指针在基础中,它似乎表明基础对象负责管理资源,而不是派生类型.
事实上,奇怪的是派生构造函数正在修改基本成员这一事实,将指针传递给基础构造函数可能更有意义,在这种情况下语义会更清晰:基础接收资源在施工期间,并负责在其生命周期内进行管理.
如果基础不应该管理资源,那么为什么指针处于该级别?为什么它不是派生类型的成员?
请注意,在管理资源时,您应该考虑三者的规则 1:如果您必须实现copy-constructor,assignment-operator或析构函数之一,那么您可能希望实现其中的三个(另外考虑实现no) - swap这将是方便的).
一旦你将复制构造函数和赋值运算符添加到混合中,你可能会看到管理资源的自然位置就在那里.
1该规则是通用的,因为有充分的理由不总是实现所有这些规则.例如,如果您在实现RAII的成员(作为智能指针)内管理您的资源,您可能需要提供copy-construction和assignment-operator以及深度复制语义,但不需要销毁.或者,在某些情况下,您可能希望禁用*copy-construction*和/或赋值.
您担心代码重复,但恐怕它影响了您的判断。
我知道 DRY 得到了很多媒体的关注(并且有充分的理由),但你误解了它。不重复代码和不重复功能之间是有区别的。
DRY是指不重复功能。可能存在看起来相似的代码模式,但用于不同的目标,这些代码模式不应合并,因为每个目标都可以独立转移(并且取消合并是一场噩梦)。他们的相似纯属巧合。
这里就是这种情况。int* ptr_您可以在Base(不使用它的)类中任意强制使用 a ,因为所有派生类肯定都需要它。你怎么知道 ?目前事实证明所有派生类都需要它,但这是一个巧合。他们可以选择依赖其他东西。
因此,明智的设计是在基类中提供一个接口,并让每个派生类选择自己的实现:
class Base { public: virtual ~Base() {} };
class D1: public Base {
public:
D1(Foo const& f): ptr_(new Foo(f)) {}
private:
std::unique_ptr<Foo> ptr_;
};
class D2: public Base {
public:
D2(Bar const& f): ptr_(new Bar(f)) {}
private:
std::unique_ptr<Bar> ptr_;
};
Run Code Online (Sandbox Code Playgroud)
另一方面,如果该类Base要提供一些需要数据成员的通用功能,那么当然可以在类实现中提升这些功能Base......尽管可以说这是不成熟的优化,因为再次,一些派生班级可能会选择以不同的方式做事。
| 归档时间: |
|
| 查看次数: |
342 次 |
| 最近记录: |