可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Does anyone know why the STL containers don't have virtual destructors?
As far as I can tell, the only benefits are:
- it reduces the size of an instance by one pointer (to the virtual method table) and
- it makes destruction and construction a tiny bit faster.
The downside is that it's unsafe to subclass the containers in the usual way.
EDIT:
Perhaps my question could be rephrased "Why weren't STL containers designed to allow for inheritance?"
Because they don't support inheritance, one is stuck with the following choices when one wants to have a new container that needs the STL functionality plus a small number of additional features (say a specialized constructor or new accessors with default values for a map, or whatever):
- Composition and interface replication: Make a new template or class that owns the STL container as a private member and has one pass-through inline method for each STL method. This is just as performant as inheritance, avoids the cost of a virtual method table (in the cases where that matters). Unfortunately, the STL containers have fairly broad interfaces so this requires many lines of code for something that should seemingly be easy to do.
- Just make functions: Use bare (possibly templated) file-scoped functions instead of trying to add member functions. In some ways this can be a good approach, but the benefits of encapsulation are lost.
- Composition with public STL access: Have the owner of the STL container let users access the STL container itself (perhaps guarded through accessors). This requires the least coding for the library writer, but it's much less convenient for users. One of the big selling points for composition is that you reduce coupling in your code, but this solution fully couples the STL container with the owner container (because the owner returns a true STL container).
- Compile-time polymorphism: Can be somewhat tricky to do write, requires some code gymnastics, and isn't appropriate for all situations.
As a side question: is there a standards-safe way of subclassing with non-virtual destructors (let's assume that I don't want to override any methods, just that I want to add new ones)? My impression is that there is no generic and safe way of doing this if one does not have the power to change the code defining the non-virtual class.
EDIT 2:
As @doc points out, C++ 11's fancier using
declarations lower the cost of composition somewhat.
回答1:
I guess it follows the C++ philosophy of not paying for features that you don't use. Depending on the platform, a pointer for the virtual table could be a hefty price to pay if you don't care about having a virtual destructor.
回答2:
A virtual destructor is only useful for inheritance scenarios. STL containers are not designed to be inherited from (nor is it a supported scenario). Hence they don't have virtual destructors.
回答3:
I think Stroustrup answered this question indirectly in his fantastic paper: Why C++ is not just an ObjectOriented Programming Language:
7 Closing Remarks
Are the various
facilities presented above
objectoriented or not? Which ones?
Using what definition of
objectoriented? In most contexts, I
think these are the wrong questions.
What matters is what ideas you can
express clearly, how easily you can
combine software from different
sources, and how efficient and
maintainable the resulting programs
are. In other words, how you support
good programming techniques and good
design techniques matters more than
labels and buzz words. The fundamental
idea is simply to improve design and
programming through abstraction. You
want to hide details, you want to
exploit any commonality in a system,
and you want to make this affordable.
I would like to encourage you not to
make objectoriented a meaningless
term. The notion of ‘‘objectoriented’’
is too frequently debased
– by
equating it with good,
– by equating
it with a single language, or
– by
accepting everything as
objectoriented.
I have argued that
there are – and must be – useful
techniques beyond objectoriented
programming and design. However, to
avoid being totally misunderstood, I
would like to emphasize that I
wouldn’t attempt a serious project
using a programming language that
didn’t at least support the classical
notion of objectoriented programming.
In addition to facilities that support
objectoriented programming, I want –
and C++ provides – features that go
beyond those in their support for
direct expression of concepts and
relationships.
STL was built with three conceptual tools in mind mainly. Generic Programming + Functional Style + Data Abstraction == STL Style. It is not strange that OOP is the not the best way to represent a Data Structure & Algorithms library. Although OOP is used in other parts of the standard library, the designer of STL saw that the mix of the three mentioned techniques is better than OOP alone. In short, the library wasn't designed with OOP in mind, and in C++ if you don't use it, it doesn't get bundled with your code. You don't pay for what you don't use. The classes std::vector, std::list,... are not OOP concepts in the Java/C# sense. They are just Abstract Data Types in the best interpretation.
回答4:
Why weren't STL containers designed to allow for inheritance?
In my humble opinion they are. If they wouldn't, they had been made final. And when I look into stl_vector.h
source I can see that my STL implementation uses protected inheritance of _Vector_base<_Tp, _Alloc>
to grant access for derived classes:
template<typename _Tp, typename _Alloc = allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>
Wouldn't it use private inheritance if subclassing was not welcome?
is there a standards-safe way of subclassing with non-virtual destructors (let's assume that I don't want to override any methods, just that I want to add new ones)?
Why not use protected
or private
inheritance and expose desired part of interface with using
keyword?
class MyVector : private std::vector<int>
{
typedef std::vector<int> Parent;
public:
using Parent::size;
using Parent::push_back;
using Parent::clear;
//and so on + of course required ctors, dtors and operators.
};
This approach ensures that the user of the class will not downcast instance to std::vector<int>
and he is safe, since the only problem with non-virtual destructor is that it won't call derived one, when object gets deleted as an instance of parent class.
...I have also loose idea, that you may even inherit publicly if your class doesn't have a destructor. Heresy?
回答5:
As has been pointed out, the STL containers are not designed to be inheritable. No virtual methods, all data members are private, no protected getters/setters/helpers.. And as you've discovered, no virtual destructors..
I'd suggest you should really be using the containers via composition rather than implementation inheritance, in a "has-a" way rather than an "is-a" one.
回答6:
you're not supposed to blindly add a virtual destructor to every class. If that were the case, the language wouldn't allow you any other option. When you add a virtual method to a class that doesn't have any other virtual methods, you just increased the size of the class instances by the size of a pointer, typically 4 bytes. That's expensive depending on what you're doing. The size increase happens because a v-table is created to hold the list of virtual methods, and each instance needs a pointer back to the v-table. It's typically located at the first cell of the instance.
回答7:
Another solution to be able to subclass from STL containers is one given by Bo Qian using smart pointers.
Advanced C++: Virtual Destructor and Smart Destructor
class Dog {
public:
~Dog() {cout << "Dog is destroyed"; }
};
class Yellowdog : public Dog {
public:
~Yellowdog() {cout << "Yellow dog destroyed." << endl; }
};
class DogFactory {
public:
static shared_ptr<Dog> createYellowDog() {
return shared_ptr<Yellowdog>(new Yellowdog());
}
};
int main() {
shared_ptr<Dog> pd = DogFactory::createYellowDog();
return 0;
}
This avoids the dillema with virtual destructors altogether.
回答8:
If you really need virtual destructor, you can add it in class derived from vector<>, and then use this class as a base class everywhere you need virtual interface. By doing this compilator will call virtual destructor from your base class, which in turn will call non-virtual destructor from vector class.
Example:
#include <vector>
#include <iostream>
using namespace std;
class Test
{
int val;
public:
Test(int val) : val(val)
{
cout << "Creating Test " << val << endl;
}
Test(const Test& other) : val(other.val)
{
cout << "Creating copy of Test " << val << endl;
}
~Test()
{
cout << "Destructing Test " << val << endl;
}
};
class BaseVector : public vector<Test>
{
public:
BaseVector()
{
cout << "Creating BaseVector" << endl;
}
virtual ~BaseVector()
{
cout << "Destructing BaseVector" << endl;
}
};
class FooVector : public BaseVector
{
public:
FooVector()
{
cout << "Creating FooVector" << endl;
}
virtual ~FooVector()
{
cout << "Destructing FooVector" << endl;
}
};
int main()
{
BaseVector* ptr = new FooVector();
ptr->push_back(Test(1));
delete ptr;
return 0;
}
This code gives following output:
Creating BaseVector
Creating FooVector
Creating Test 1
Creating copy of Test 1
Destructing Test 1
Destructing FooVector
Destructing BaseVector
Destructing Test 1
回答9:
No virtual destructor prevents the class from being subclasses correctly.