Subclass/inherit standard containers? [closed]

≡放荡痞女 提交于 2019-11-25 22:48:45

问题


I often read this statements on Stack Overflow. Personally, I don\'t find any problem with this, unless I am using it in a polymorphic way; i.e. where I have to use virtual destructor.

If I want to extend/add the functionality of a standard container then what is a better way than inheriting one? Wrapping those container inside a custom class requires much more effort and is still unclean.


回答1:


There are a number of reasons why this a bad idea.

First, this is a bad idea because the standard containers do not have virtual destructors. You should never use something polymorphically that does not have virtual destructors, because you cannot guarantee cleanup in your derived class.

Basic rules for virtual dtors

Second, it is really bad design. And there are actually several reasons it is bad design. First, you should always extend the functionality of standard containers through algorithms that operate generically. This is a simple complexity reason - if you have to write an algorithm for every container it applies to and you have M containers and N algorithms, that is M x N methods you must write. If you write your algorithms generically, you have N algorithms only. So you get much more reuse.

It is also really bad design because you are breaking a good encapsulation by inheriting from the container. A good rule of thumb is: if you can perform what you need using the public interface of a type, make that new behavior external to the type. This improves encapsulation. If it's a new behavior you want to implement, make it a namespace scope function (like the algorithms). If you have a new invariant to impose, use containment in a class.

A classic description of encapsulation

Finally, in general, you should never think about inheritance as a means to extend the behavior of a class. This is one of the big, bad lies of early OOP theory that came about due to unclear thinking about reuse, and it continues to be taught and promoted to this day even though there is a clear theory why it is bad. When you use inheritance to extend behavior, you are tying that extended behavior to your interface contract in a way that ties users hands to future changes. For instance, say you have a class of type Socket that communicates using the TCP protocol and you extend it's behavior by deriving a class SSLSocket from Socket and implementing the behavior of the higher SSL stack protocol on top of Socket. Now, let's say you get a new requirement to have the same protocol of communications, but over a USB line, or over telephony. You would need to cut and paste all that work to a new class that derives from a USB class, or a Telephony class. And now, if you find a bug, you have to fix it in all three places, which won't always happen, which means bugs will take longer and not always get fixed...

This is general to any inheritance hierarchy A->B->C->... When you want to use the behaviors you've extended in derived classes, like B, C, .. on objects not of the base class A, you've got to redesign or you are duplicating implementation. This leads to very monolithic designs that are very hard to change down the road (think Microsoft's MFC, or their .NET, or - well, they make this mistake a lot). Instead, you should almost always think of extension through composition whenever possible. Inheritance should be used when you are thinking "Open / Closed Principle". You should have abstract base classes and dynamic polymorphism runtime through inherited class, each will full implementations. Hierarchies shouldn't be deep - almost always two levels. Only use more than two when you have different dynamic categories that go to a variety of functions that need that distinction for type safety. In those cases, use abstract bases until the leaf classes, which have the implementation.




回答2:


May be many people here will not like this answer, but it is time for some heresy to be told and yes ... be told also that "the king is naked!"

All the motivation against the derivation are weak. Derivation is not different than composition. It's just a way to "put things together". Composition puts things together giving them names, inheritance does it without giving explicit names.

If you need a vector that has the same interface and implementation of std::vect plus something more, you can:

use composition and rewrite all the embedded object function prototypes implementing function that delegates them (and if they are 10000... yes: be prepared to rewrite all those 10000) or...

inherit it and add just what you need (and ... just rewrite constructors, until C++ lawyers will decide to let them be inheritable as well: I still remember 10 year ago zealot discussion about "why ctors cannot call each other" and why it is a "bad bad bad thing" ... until C++11 permitted it and suddenly all those zealots shut up!) and let the new destructor non virtual as it was the original one.

Just like for every class that has some virtual method and some not, you know you cannot pretend to invoke the non virtual method of derived by addressing the base, the same applies for delete. There is no reason just for delete to pretend any particular special care. A programmer that knows that what is not virtual isn't callable addressing the base also knows not to use delete on your base after allocating your derived.

All the "avoid this" "don't do that" always sound as "moralization" of something that is natively agnostic. All the features of a language exist to solve some problem. The fact a given way to solve the problem is good or bad depends on the context, not on the feature itself. If what you're doing needs to serve many container, inheritance is probably not the way (you have to redo for all). If it is for a specific case ... inheritance is a way to compose. Forget OOP purisms: C++ is not a "pure OOP" and container are not OOP at all.




回答3:


You should refrain from deriving publicly from standard contianers. You may choose between private inheritance and composition and it seems to me that all the general guidelines indicate that composition is better here since you don't override any function. Don't derive publicly form STL containers - there really isn't any need of it.

By the way, if you want to add a bunch of algorithms to the container, consider adding them as freestanding functions taking an iterator range.




回答4:


Publicly inheriting is a problem for all the reasons others have stated, namely that your container can be upcasted to the base class which does not have a virtual destructor or virtual assignment operator, which can lead to slicing problems.

Privately inheriting, on the other hand, is less of an issue. Consider the following example:

#include <vector>
#include <iostream>

// private inheritance, nobody else knows about the inheritance, so nobody is upcasting my
// container to a std::vector
template <class T> class MyVector : private std::vector<T>
{
private:
    // in case I changed to boost or something later, I don't have to update everything below
    typedef std::vector<T> base_vector;

public:
    typedef typename base_vector::size_type       size_type;
    typedef typename base_vector::iterator        iterator;
    typedef typename base_vector::const_iterator  const_iterator;

    using base_vector::operator[];

    using base_vector::begin;
    using base_vector::clear;
    using base_vector::end;
    using base_vector::erase;
    using base_vector::push_back;
    using base_vector::reserve;
    using base_vector::resize;
    using base_vector::size;

    // custom extension
    void reverse()
    {
        std::reverse(this->begin(), this->end());
    }
    void print_to_console()
    {
        for (auto it = this->begin(); it != this->end(); ++it)
        {
            std::cout << *it << '\n';
        }
    }
};


int main(int argc, char** argv)
{
    MyVector<int> intArray;
    intArray.resize(10);
    for (int i = 0; i < 10; ++i)
    {
        intArray[i] = i + 1;
    }
    intArray.print_to_console();
    intArray.reverse();
    intArray.print_to_console();

    for (auto it = intArray.begin(); it != intArray.end();)
    {
        it = intArray.erase(it);
    }
    intArray.print_to_console();

    return 0;
}

OUTPUT:

1
2
3
4
5
6
7
8
9
10
10
9
8
7
6
5
4
3
2
1

Clean and simple, and gives you the freedom to extend std containers without much effort.

And if you think about doing something silly, like this:

std::vector<int>* stdVector = &intArray;

You get this:

error C2243: 'type cast': conversion from 'MyVector<int> *' to 'std::vector<T,std::allocator<_Ty>> *' exists, but is inaccessible



回答5:


The problem is that you, or someone else, might accidentally pass your extended class to a function expecting a reference to the base class. That will effectively (and silently!) slice off the extensions and create some hard to find bugs.

Having to write some forwarding functions seems like a small price to pay in comparison.




回答6:


Because you can never guarantee that you haven't used them in a polymorphic way. You're begging for problems. Taking the effort to write a few functions is no big deal, and, well, even wanting to do this is dubious at best. What happened to encapsulation?




回答7:


Most common reason to want to inherit from the containers is because you want to add some member function to the class. Since stdlib itself is not modifiable, inheritance is thought to be the substitute. This does not work however. It's better to do a free function that takes a vector as parameter:

void f(std::vector<int> &v) { ... }



回答8:


I occasionally inherit from collection types simply as a better way to name types.
I don't like typedef as a matter of personal preference. So I will do something like:

class GizmoList : public std::vector<CGizmo>
{
    /* No Body & no changes.  Just a more descriptive name */
};

Then it is much easier and clearer to write:

GizmoList aList = GetGizmos();

If you start adding methods to GizmoList instead, you may well run into trouble.




回答9:


IMHO, I don't find any harm in inheriting STL containers if they are used as functionality extensions. (That's why I asked this question. :) )

The potential problem can occur when you try to pass the pointer/reference of your custom container to a standard container.

template<typename T>
struct MyVector : std::vector<T> {};

std::vector<int>* p = new MyVector<int>;
//....
delete p; // oops "Undefined Behavior"; as vector::~vector() is not 'virtual'

Such problems can be avoided consciously, provided good programming practice is followed.

If I want to take extreme care then I can go upto this:

#include<vector>
template<typename T>
struct MyVector : std::vector<T> {};
#define vector DONT_USE

Which will disallow using vector entirely.



来源:https://stackoverflow.com/questions/6806173/subclass-inherit-standard-containers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!