C++ 从 STL 容器继承实现而不是委托可以吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2034916/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it okay to inherit implementation from STL containers, rather than delegate?
提问by Emile Cormier
I have a class that adapts std::vector to model a container of domain-specific objects. I want to expose most of the std::vector API to the user, so that he/she may use familiar methods (size, clear, at, etc...) and standard algorithms on the container. This seems to be a reoccurring pattern for me in my designs:
我有一个类,它采用 std::vector 来建模域特定对象的容器。我想向用户公开大部分 std::vector API,以便他/她可以在容器上使用熟悉的方法(大小、清除、at 等)和标准算法。这似乎是我设计中反复出现的模式:
class MyContainer : public std::vector<MyObject>
{
public:
// Redeclare all container traits: value_type, iterator, etc...
// Domain-specific constructors
// (more useful to the user than std::vector ones...)
// Add a few domain-specific helper methods...
// Perhaps modify or hide a few methods (domain-related)
};
I'm aware of the practice of preferring composition to inheritance when reusing a class for implementation -- but there's gotta be a limit! If I were to delegate everything to std::vector, there would be (by my count) 32 forwarding functions!
我知道在重用类进行实现时更喜欢组合而不是继承的做法——但必须有一个限制!如果我将所有内容都委托给 std::vector,那么(按我的统计)将有 32 个转发函数!
So my questions are... Is it really so bad to inherit implementation in such cases? What are the risks? Is there a safer way I can implement this without so much typing? Am I a heretic for using implementation inheritance? :)
所以我的问题是......在这种情况下继承实现真的很糟糕吗?有哪些风险?有没有更安全的方法可以在没有这么多输入的情况下实现它?我是使用实现继承的异端吗?:)
Edit:
编辑:
What about making it clear that the user should not use MyContainer via a std::vector<> pointer:
如何明确用户不应通过 std::vector<> 指针使用 MyContainer:
// non_api_header_file.h
namespace detail
{
typedef std::vector<MyObject> MyObjectBase;
}
// api_header_file.h
class MyContainer : public detail::MyObjectBase
{
// ...
};
The boost libraries seem to do this stuff all the time.
boost 库似乎一直在做这些事情。
Edit 2:
编辑2:
One of the suggestions was to use free functions. I'll show it here as pseudo-code:
建议之一是使用免费功能。我将在此处显示为伪代码:
typedef std::vector<MyObject> MyCollection;
void specialCollectionInitializer(MyCollection& c, arguments...);
result specialCollectionFunction(const MyCollection& c);
etc...
A more OO way of doing it:
一种更面向对象的方式:
typedef std::vector<MyObject> MyCollection;
class MyCollectionWrapper
{
public:
// Constructor
MyCollectionWrapper(arguments...) {construct coll_}
// Access collection directly
MyCollection& collection() {return coll_;}
const MyCollection& collection() const {return coll_;}
// Special domain-related methods
result mySpecialMethod(arguments...);
private:
MyCollection coll_;
// Other domain-specific member variables used
// in conjunction with the collection.
}
采纳答案by Emile Cormier
The risk is deallocating through a pointer to the base class(delete, delete[], and potentially other deallocation methods). Since these classes (deque, map, string, etc.) don't have virtual dtors, it's impossible to clean them up properly with only a pointer to those classes:
风险是通过指向基类的指针(delete、delete[]和其他潜在的释放方法)进行释放。由于这些类(deque、map、string等)没有虚拟 dtor,因此仅使用指向这些类的指针是不可能正确清理它们的:
struct BadExample : vector<int> {};
int main() {
vector<int>* p = new BadExample();
delete p; // this is Undefined Behavior
return 0;
}
That said, ifyou're willing to make sure you never accidentally do this, there's little major drawback to inheriting them—but in some cases that's a big if. Other drawbacks include clashing with implementation specifics and extensions (some of which may not use reserved identifiers) and dealing with bloated interfaces (stringin particular). However, inheritance is intended in some cases, as container adapters like stackhave a protected member c(the underlying container they adapt), and it's almost only accessible from a derived class instance.
也就是说,如果你愿意确保你永远不会意外地这样做,继承它们没有什么大的缺点——但在某些情况下,这是一个很大的问题。其他缺点包括与实现细节和扩展(其中一些可能不使用保留标识符)发生冲突以及处理臃肿的接口(特别是字符串)。但是,在某些情况下需要继承,因为像stack这样的容器适配器有一个受保护的成员c(它们适应的底层容器),并且它几乎只能从派生类实例访问。
Instead of either inheritance or composition, consider writing free functionswhich take either an iterator pair or a container reference, and operate on that. Practically all of <algorithm> is an example of this; and make_heap, pop_heap, and push_heap, in particular, are an example of using free functions instead of a domain-specific container.
与其使用继承或组合,不如考虑编写采用迭代器对或容器引用的自由函数,并对其进行操作。实际上所有 <algorithm> 都是一个例子;和make_heap,pop_heap和push_heap,特别是使用,而不是一个特定于域的容器自由函数的一个例子。
So, use the container classes for your data types, and still call the free functions for your domain-specific logic. But you can still achieve some modularity using a typedef, which allows you to both simplify declaring them and provides a single point if part of them needs to change:
因此,请为您的数据类型使用容器类,并仍然为您的域特定逻辑调用免费函数。但是您仍然可以使用 typedef 实现一些模块化,这允许您简化声明它们并在它们的一部分需要更改时提供一个点:
typedef std::deque<int, MyAllocator> Example;
// ...
Example c (42);
example_algorithm(c);
example_algorithm2(c.begin() + 5, c.end() - 5);
Example::iterator i; // nested types are especially easier
Notice the value_type and allocator can change without affecting later code using the typedef, and even the container can change from a dequeto a vector.
注意 value_type 和 allocator 可以更改,而不会影响使用 typedef 的后续代码,甚至容器也可以从deque更改为vector。
回答by Ben
You can combine private inheritance and the 'using' keyword to work around most of the problems mentioned above: Private inheritance is 'is-implemented-in-terms-of' and as it is private you cannot hold a pointer to the base class
您可以结合使用私有继承和 'using' 关键字来解决上面提到的大多数问题:私有继承是 'is-implemented-in-terms-of',因为它是私有的,所以不能持有指向基类的指针
#include <string>
#include <iostream>
class MyString : private std::string
{
public:
MyString(std::string s) : std::string(s) {}
using std::string::size;
std::string fooMe(){ return std::string("Foo: ") + *this; }
};
int main()
{
MyString s("Hi");
std::cout << "MyString.size(): " << s.size() << std::endl;
std::cout << "MyString.fooMe(): " << s.fooMe() << std::endl;
}
回答by D.Shawley
As everyone has already stated, STL containers do not have virtual destructors so inheriting from them is unsafe at best. I've always considered generic programming with templates as a different style of OO - one without inheritance. The algorithms define the interface that they require. It is as close to Duck Typingas you can get in a static language.
正如每个人已经说过的,STL 容器没有虚拟析构函数,因此从它们继承充其量是不安全的。我一直认为使用模板进行泛型编程是一种不同的 OO 风格——一种没有继承的风格。算法定义了它们需要的接口。它与静态语言中的Duck Typing非常接近。
Anyway, I do have something to add to the discussion. The way that I have created my own template specializations previously is to define classes like the following to use as base classes.
无论如何,我确实有一些东西要添加到讨论中。我之前创建自己的模板特化的方法是定义如下类以用作基类。
template <typename Container>
class readonly_container_facade {
public:
typedef typename Container::size_type size_type;
typedef typename Container::const_iterator const_iterator;
virtual ~readonly_container_facade() {}
inline bool empty() const { return container.empty(); }
inline const_iterator begin() const { return container.begin(); }
inline const_iterator end() const { return container.end(); }
inline size_type size() const { return container.size(); }
protected: // hide to force inherited usage only
readonly_container_facade() {}
protected: // hide assignment by default
readonly_container_facade(readonly_container_facade const& other):
: container(other.container) {}
readonly_container_facade& operator=(readonly_container_facade& other) {
container = other.container;
return *this;
}
protected:
Container container;
};
template <typename Container>
class writable_container_facade: public readable_container_facade<Container> {
public:
typedef typename Container::iterator iterator;
writable_container_facade(writable_container_facade& other)
readonly_container_facade(other) {}
virtual ~writable_container_facade() {}
inline iterator begin() { return container.begin(); }
inline iterator end() { return container.end(); }
writable_container_facade& operator=(writable_container_facade& other) {
readable_container_facade<Container>::operator=(other);
return *this;
}
};
These classes expose the same interface as an STL container. I did like the effect of separating the modifying and non-modifying operations into distinct base classes. This has a really nice effect on const-correctness. The one downside is that you have to extend the interface if you want to use these with associative containers. I haven't run into the need though.
这些类公开与 STL 容器相同的接口。我确实喜欢将修改和非修改操作分成不同基类的效果。这对常量正确性有非常好的影响。一个缺点是,如果要将这些与关联容器一起使用,则必须扩展接口。不过我还没有遇到这种需要。
回答by stijn
In this case, inheriting is a bad idea: the STL containers do not have virtual destructors so you might run into memory leaks (plus, it's an indication that STL containers are not meant to be inherited in the first place).
在这种情况下,继承是一个坏主意:STL 容器没有虚拟析构函数,因此您可能会遇到内存泄漏(另外,这表明 STL 容器不应该首先被继承)。
If you just need to add some functionality, you can declare it in global methods, or a lightweight class with a container member pointer/reference. This off course doesn't allow you to hide methods: if that is really what you are after, then there's no other option then redeclaring the entire implementation.
如果你只需要添加一些功能,你可以在全局方法中声明它,或者一个带有容器成员指针/引用的轻量级类。这当然不允许您隐藏方法:如果这确实是您所追求的,那么没有其他选择然后重新声明整个实现。
回答by Jherico
Virtual dtors aside, the decision to inherit versus contain should be a design decision based the class you are creating. You should never inherit container functionality just because its easier than containing a container and adding a few add and remove functions that seem like simplistic wrappers unlessyou can definitively say that the class you are creating is a kind-of the container. For instance, a classroom class will often contain student objects, but a classroom isn't a kind of list of students for most purposes, so you shouldn't be inheriting from list.
除了虚拟 dtor,继承还是包含的决定应该是基于您正在创建的类的设计决定。您永远不应该仅仅因为它比包含容器和添加一些看似简单的包装器的添加和删除函数更容易而继承容器功能,除非您可以明确地说您正在创建的类是一种容器。例如,课堂类通常会包含学生对象,但在大多数情况下,课堂并不是一种学生列表,因此您不应该从列表继承。
回答by Charles Eli Cheese
The forwarding methods will be inlined away, anyhow. You will not get better performance this way. In fact, you will likely get worse performance.
无论如何,转发方法将被内联。您不会以这种方式获得更好的性能。事实上,您可能会获得更差的性能。
回答by Martin York
It is easier to do:
这样做更容易:
typedef std::vector<MyObject> MyContainer;