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

发布于 2024-09-17 05:26:41 字数 657 浏览 2 评论 0 原文

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

相册主要视为具有一些元数据(标题和时间)和相册特定功能的图片集合,例如将某些照片的缩略图渲染到表面上以使专辑封面。所以恕我直言,相册是照片的集合,而不是它有这样的集合。

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

One of my C++ classes derives from std::vector so that it can act as a container that also perform custom actions on its content. Unfortunately, the compiler complains about the destructor not to be virtual, which I cannot change, since its in the standard library.

Am I doing the whole thing wrong (thou shall not derive from STL) or is there something I could do to keep the compiler happy ? (appart from stop using -Weffc++ :)

edit: the derived class do not touch the vector-manipulation algorithms, but merely add some information such as "element width/height" for a vector of images. As an example, you could think of

class PhotoAlbum: public std::vector<Photo> {
    String title;
    Date from_time, to_time;
    // accessors for title and dates
    void renderCover(Drawable &surface);
};

where you think of a photo album primarily as a collection of pictures with some meta-data (title and time) and album-specific features such as rendering a thumbnail of some Photo onto a surface to make the album cover. So imho, the photo album IS-A collection of Photo, more than it HAS-A such collection.

I fail to see any benefit I'd gain of having getPhotoVector() method in a PhotoAlbum that would have an extra "collection" field.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

無心 2024-09-24 05:26:50

我是不是做错了

您说您从 vector 派生来提供特殊功能。通常完成此操作的方法是结合使用内置算法和您自己的函子来提供此特殊功能。

这样做而不是从 vector 派生可以带来多种好处:

1) 它有助于防止范围蔓延到 vectorvector 的工作是维护对象的集合。不要对这些对象执行算法功能。通过从 vector 派生并添加特殊函数,您可以使 vector 变得更加复杂,因为现在您给了它另一项工作要做。

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

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

4) 整体设计更简单,因此不易出现缺陷且更易于维护。

Am I doing the whole thing wrong

Possibly.

You say you have derived from vector in order to provide special functionality. The way this is typically done is by using the built-in algorithms in combination with your own functors to provide this special functionality.

Doing this instead of deriving from vector provides multiple benefits:

1) It helps to prevent scope creep in to vector. vector's job is to maintain a collection of objects. Not to perform algorithmic functions on those objects. By deriving from vector and adding your special functions, you make vector more complex because now you have given it another job to do.

2) It is more in line with the "STL way" of doing things. This makes it easier to maintain in the future by people who know the STL but who may not know your special class which behaves differently than the STL collections.

3) It is more extensible. A properly written functor doesn't care what kind of collection it acts upon. If for some reason you one day want to use a list instead of a vector, if you're using functors it is a much simpler matter to refactor than to reimplement a new special list-derived class.

4) It is overall a simpler design, and therefore less susceptible to defects and easier to maintain.

潇烟暮雨 2024-09-24 05:26:49

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

Maybe use composition instead of inheritance? You don't really say why you are extending vector so I don't know if this is an option.

北陌 2024-09-24 05:26:47

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

为了解决这个问题,而又不必向用户强加如此奇怪的条件,最好的建议是,对于 C++ 中的公共基类,析构函数应该是公共和虚拟的,或者是受保护的和非虚拟的 (http://www.gotw.ca/publications/mill18.htm,准则#4)。由于 std::vector 的析构函数都不是,这意味着它不应该用作公共基类。

如果您只想在向量上定义一些附加操作,那么这就是 C++ 中自由函数的用途。 . 成员调用语法到底有什么好处呢?大部分 由向量和其他容器上的附加操作组成。

例如,如果您想创建一个“具有最大大小限制的向量”,它将为 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 中的重载,因此我更改的函数根本不会应用 - 这是非虚拟的为您提供功能。我不能只是在 myvec 中重载 resize() 并完成它,我必须重载每个更改大小的函数并使它们全部调用 check (直接或通过互相调用)。

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

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

当然,如果您使用私有继承,则无法将 myvec 传递给需要向量的函数。但这是因为它不是一个向量 - 它的push_back函数甚至没有与向量相同的语义,所以我们对LSP的理解是不可靠的,但是更重要的是,对 vector 函数的非虚拟调用会忽略我们的重载。如果您按照标准库预期的方式做事 - 使用大量模板并传递迭代器而不是集合,那就没问题了。如果您想要虚函数调用,那就不好了,因为除了 vector 没有虚析构函数这一事实之外,它也没有任何虚函数。

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

It can be safe to have a public base class with a non-virtual destructor but behaviour is undefined if someone allocates an instance of your class with new, refers to it with a vector<...>*, and then deletes it using that pointer without casting it back to a pointer to your class. So users of your class must know not to do that. The surest way to stop them is not to give them the opportunity, hence the compiler warning.

To deal with this issue without having to impose such odd conditions on your users, the best advice is that for public base classes in C++, the destructor should be either public and virtual, or protected and non-virtual (http://www.gotw.ca/publications/mill18.htm, guideline #4). Since the destructor of std::vector is neither, that means it shouldn't be used as a public base class.

If all you want is to define some additional operations on vectors, then that's what free functions are for in C++. What's so great about the . member-call syntax anyway? Most of <algorithm> consists of additional operations on vector and other containers.

If you want to create, for example, a "vector with a maximum size limit", which will provide the entire interface of vector with modified semantics, then actually C++ does make that slightly inconvenient, compared with languages where inheritance and virtual calls are the norm. Simplest is to use private inheritance and then for the member functions of vector that you don't want to change, bring them into your class with 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
}

You still have to be careful, though. The C++ standard doesn't guarantee which functions of vector call each other, or in what ways. Where those calls do occur, there's no way for my vector base class to call the overload in myvec, so my changed functions simply won't apply -- that's non-virtual functions for you. I can't just overload resize() in myvec and be done with it, I have to overload every function that changes the size and make them all call check (directly or by calling each other).

You can deduce from restrictions in the standard that some things are impossible: for example, operator[] can't change the size of the vector, so in my example I'm safe to use the base-class implementation, and I only have to overload functions which might change the size. But the standard won't necessarily provide guarantees of that kind for all conceivable derived classes.

In short, std::vector is not designed to be a base class, and hence it may not be a very well-behaved base class.

Of course, if you use private inheritance then you can't pass myvec to a function that requires a vector. But that's because it isn't a vector - its push_back function doesn't even have the same semantics as a vector, so we're on dodgy grounds with LSP, but more importantly non-virtual calls to functions of vector ignore our overloads. That's OK if you do things the way the standard libraries anticipate - use lots of templates, and pass iterators rather than collections. It's not OK if you want virtual function calls, because quite aside from the fact that vector doesn't have a virtual destructor, it doesn't have any virtual functions.

If you actually want dynamic polymorphism with standard containers (that is, you want to do
vector<int> *ptr = new myvec(1);), then you're entering "thou shalt not" territory. The standard libraries can't really help you.

朕就是辣么酷 2024-09-24 05:26:46

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

Why not use composition? Simply make std::vector a member of your custom container, then implement the custom actions as member functions of said class that act upon the std::vector member. That way, you have complete control over it. Besides, you should prefer composition over inheritance if inheritance is not required.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文