我的一个C ++类派生自std::vector
,因此它可以充当一个容器,同时对其内容执行自定义操作。不幸的是,编译器抱怨析构函数不是虚拟的,我无法改变它,因为它在标准库中。
我做错了什么(你不应该从STL派生)或者我能做些什么来保持编译器的快乐? (从停止使用-Weffc ++开始:)
编辑:派生类不接触矢量操作算法,而只是为图像矢量添加一些信息,如“元素宽度/高度”。举个例子,你可以想到
class PhotoAlbum: public std::vector<Photo> {
String title;
Date from_time, to_time;
// accessors for title and dates
void renderCover(Drawable &surface);
};
您认为相册主要是带有一些元数据(标题和时间)和专辑特定功能的图片集合,例如将某些照片的缩略图渲染到曲面上以制作专辑封面。所以imho,相册IS-A Photo
的集合,比它更像是HAS-A这样的集合。
我没有看到在getPhotoVector()
中使用PhotoAlbum
方法获得额外“收集”字段所带来的任何好处。
答案 0 :(得分:16)
为什么不使用作文?只需使std::vector
成为自定义容器的成员,然后将自定义操作实现为作为std::vector
成员的所述类的成员函数。这样,您就可以完全控制它。此外,you should prefer composition over inheritance如果不需要继承。
答案 1 :(得分:10)
如果某人使用new
分配了您的类的实例,那么使用非虚拟析构函数的公共基类是安全的,但是行为是未定义的。 vector<...>*
,然后使用该指针删除它,而不将其强制转换为指向类的指针。因此,您班级的用户必须知道不要这样做。阻止他们的最可靠方法是不给他们机会,因此编译警告。
要处理这个问题而不必对用户强加这样的奇怪条件,最好的建议是对于C ++中的公共基类,析构函数应该是公共的和虚拟的,或者是受保护的和非虚拟的({{3指南#4)。由于std::vector
的析构函数都不是,这意味着它不应该用作公共基类。
如果你想要的只是在向量上定义一些额外的操作,那么这就是C ++中的自由函数。无论如何,.
成员调用语法有什么好处?大多数<algorithm>
包含对向量和其他容器的附加操作。
如果你想创建一个“带有最大大小限制的向量”,它将为vector
的整个接口提供修改后的语义,那么与语言相比,实际上C ++确实有点不方便继承和虚拟调用是常态。最简单的方法是使用私有继承,然后对于您不想更改的vector
成员函数,使用using
将它们带入您的班级:
#include <vector>
#include <iostream>
#include <stdexcept>
class myvec : private std::vector<int> {
size_t max_size;
public:
myvec(size_t m) : max_size(m) {}
// ... other constructors
void push_back(int i) {
check(size()+1);
std::vector<int>::push_back(i);
}
// ... other modified functions
using std::vector<int>::operator[];
// ... other unmodified functions
private:
void check(size_t newsize) {
if (newsize > max_size) throw std::runtime_error("limit exceeded");
}
};
int main() {
myvec m(1);
m.push_back(3);
std::cout << m[0] << "\n";
m.push_back(3); // throws an exception
}
但是,你仍然需要小心。 C ++标准不保证vector
的哪些函数相互调用,或以何种方式调用。在那些调用确实发生的地方,我的vector
基类无法调用myvec
中的重载,因此我更改的函数根本不适用 - 这对您来说是非虚函数。我不能只在resize()
重载myvec
并完成它,我必须重载每个改变大小的函数并使它们全部调用check
(直接或通过相互调用)。
您可以从标准中的限制中推断出某些事情是不可能的:例如,operator[]
无法更改向量的大小,因此在我的示例中我可以安全地使用基类实现,我只需要重载可能会改变大小的函数。但是,标准不一定能为所有可想到的派生类提供这种保证。
简而言之,std::vector
并非设计为基类,因此它可能不是一个表现良好的基类。
当然,如果使用私有继承,则无法将myvec
传递给需要向量的函数。但那是因为它不是一个向量 - 它的push_back
函数甚至没有与向量相同的语义,所以我们在LSP上有狡猾的理由,但更重要的是非 - 对vector
函数的虚拟调用忽略了我们的重载。如果按照标准库预期的方式执行操作,那就没关系 - 使用大量模板,并传递迭代器而不是集合。如果你想要虚拟函数调用,那就不行了,因为除了vector
没有虚拟析构函数之外,它没有任何虚函数。
如果您确实希望使用标准容器进行动态多态(也就是说,您希望这样做
vector<int> *ptr = new myvec(1);
),然后你进入“你不应该”领土。标准库无法真正帮助您。
答案 2 :(得分:1)
也许使用构图而不是继承?你没有真正说出为什么要扩展向量,所以我不知道这是否是一个选项。
答案 3 :(得分:1)
我做错了吗
可能。
您说您来自vector
以提供特殊功能。通常通过将内置算法与您自己的仿函数结合使用来提供此特殊功能。
执行此操作而不是从vector
派生,可带来多重好处:
1)它有助于防止范围蔓延到vector
。 vector
的工作是维护一组对象。不要对这些对象执行算法功能。通过派生vector
并添加您的特殊功能,您可以使vector
更复杂,因为现在您已经完成了另一项工作。
2)它更符合做事的“STL方式”。这使得知道STL但可能不知道您的特殊类与STL集合的行为不同的人在将来更容易维护。
3)它更具可扩展性。一个写得很好的仿函数并不关心它的作用是什么样的集合。如果由于某种原因你有一天想要使用list
而不是vector
,如果你使用仿函数,那么重构要比重新实现一个新的特殊list
要简单得多。 - 来自上课。
4)整体设计更简单,因此不易受缺陷影响,更易于维护。