基类'class std :: vector< ...>'有一个非虚拟析构函数

时间:2010-08-30 14:47:07

标签: c++ vector stl virtual-destructor

我的一个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方法获得额外“收集”字段所带来的任何好处。

4 个答案:

答案 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)它有助于防止范围蔓延到vectorvector的工作是维护一组对象。不要对这些对象执行算法功能。通过派生vector并添加您的特殊功能,您可以使vector更复杂,因为现在您已经完成了另一项工作。

2)它更符合做事的“STL方式”。这使得知道STL但可能不知道您的特殊类与STL集合的行为不同的人在将来更容易维护。

3)它更具可扩展性。一个写得很好的仿函数并不关心它的作用是什么样的集合。如果由于某种原因你有一天想要使用list而不是vector,如果你使用仿函数,那么重构要比重新实现一个新的特殊list要简单得多。 - 来自上课。

4)整体设计更简单,因此不易受缺陷影响,更易于维护。