2

我的一个 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);
};

您将相册主要视为具有一些元数据(标题和时间)和相册特定功能的图片集合,例如将某些照片的缩略图渲染到表面上以制作相册封面。所以恕我直言,相册 IS-A 的集合Photo,比它 HAS-A 这样的集合还要多。

我看不到在具有额外“集合”字段的getPhotoVector()方法中获得的任何好处。PhotoAlbum

4

4 回答 4

16

为什么不使用合成?只需创建std::vector自定义容器的成员,然后将自定义操作实现为作用于该成员的所述类的成员函数std::vector。这样,您就可以完全控制它。此外,如果不需要继承,您应该更喜欢组合而不是继承。

于 2010-08-30T14:49:27.680 回答
10

拥有一个带有非虚拟析构函数的公共基类是安全的,但是如果有人使用 分配您的类的实例,new使用 a 引用它vector<...>*,然后使用该指针将其删除而不将其转换回指针,则行为是不确定的到你的班级。因此,您班级的用户必须知道不要这样做。阻止它们的最可靠方法是不给它们机会,因此会发出编译器警告。

为了解决这个问题而不必对您的用户施加如此奇怪的条件,最好的建议是对于 C++ 中的公共基类,析构函数应该是公共的和虚拟的,或者受保护的和非虚拟的(http://www. gotw.ca/publications/mill18.htm,准则 #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基类无法调用 in 中的重载myvec,因此我更改的函数根本不适用——这对您来说是非虚拟函数。我不能只是重载resize()myvec完成它,我必须重载每个改变大小的函数并使它们全部调用check(直接或通过相互调用)。

你可以从标准中的限制推断出有些事情是不可能的:例如,operator[]不能改变向量的大小,所以在我的例子中我可以安全地使用基类实现,我只需要重载函数这可能会改变大小。但是该标准不一定会为所有可能的派生类提供这种保证。

简而言之,std::vector它并非设计为基类,因此它可能不是一个表现良好的基类。

当然,如果您使用私有继承,则无法传递myvec给需要向量的函数。但那是因为它不是向量——它的push_back函数甚至没有与向量相同的语义,所以我们在 LSP 的基础上存在问题,但更重要的是,对函数的非虚拟调用vector忽略了我们的重载。如果你按照标准库的预期做事,那就没问题了——使用大量模板,并传递迭代器而不是集合。如果你想要虚函数调用是不行的,因为除了vector没有虚析构函数之外,它没有任何虚函数。

如果你真的想要标准容器的动态多态性(也就是说,你想做 vector<int> *ptr = new myvec(1);),那么你就进入了“你不应该”的领域。标准库不能真正帮助你。

于 2010-08-30T19:36:18.793 回答
1

也许使用组合而不是继承?您并没有真正说明为什么要扩展向量,所以我不知道这是否是一种选择。

于 2010-08-30T14:50:41.690 回答
1

我做错了吗

可能。

你说你派生出来vector是为了提供特殊的功能。这通常是通过将内置算法与您自己的函子结合使用来提供此特殊功能。

这样做而不是派生自vector提供了多种好处:

1) 它有助于防止范围蔓延到vector. vector的工作是维护对象的集合。不对这些对象执行算法功能。通过派生vector和添加您的特殊功能,您会变得vector更加复杂,因为现在您已经赋予它另一项工作要做。

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

3) 扩展性更强。一个正确编写的函子并不关心它作用于什么样的集合。如果出于某种原因您有一天想要使用 alist而不是 a vector,如果您使用的是仿函数,那么重构比重新实现一个新的特殊list派生类要简单得多。

4) 总体上是一个更简单的设计,因此不易出现缺陷并且更易于维护。

于 2010-08-30T14:56:39.287 回答