我有一个类,它使std :: vector适用于为特定于域的对象的容器建模。我想向用户公开大多数std :: vector API,以便他/她可以在容器上使用熟悉的方法(大小,清晰,等等......)和标准算法。在我的设计中,这似乎是一种反复出现的模式:
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)
};
我知道在重用类实现时更喜欢使用组合继承的做法 - 但是必须有限制!如果我将所有内容委托给std :: vector,那么(按我的计数)将有32个转发函数!
所以我的问题是......在这种情况下继承实施真的很糟糕吗?有什么风险?有没有更安全的方式我可以在没有这么多打字的情况下实现这一点?我是使用实现继承的异教徒吗? :)
修改
如何明确表示用户不应通过std :: vector&lt;&gt;使用MyContainer指针:
// non_api_header_file.h
namespace detail
{
typedef std::vector<MyObject> MyObjectBase;
}
// api_header_file.h
class MyContainer : public detail::MyObjectBase
{
// ...
};
升级库似乎一直在做这些事情。
编辑2:
其中一个建议是使用免费功能。我将在这里将其显示为伪代码:
typedef std::vector<MyObject> MyCollection;
void specialCollectionInitializer(MyCollection& c, arguments...);
result specialCollectionFunction(const MyCollection& c);
etc...
更多的OO方式:
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.
}
答案 0 :(得分:69)
风险是通过指向基类的指针(删除,删除[] 以及其他可能的解除分配方法)释放。由于这些类( deque , map , string 等)没有虚拟存储器,因此无法仅使用虚拟存储器进行正确清理指向这些类的指针:
struct BadExample : vector<int> {};
int main() {
vector<int>* p = new BadExample();
delete p; // this is Undefined Behavior
return 0;
}
那就是说,如果你愿意确保你不会意外地这样做,那么继承它们就没有什么大的缺点 - 但在某些情况下这是一个很大的问题。其他缺点包括与实现细节和扩展冲突(其中一些可能不使用保留标识符)和处理膨胀接口(特别是 string )。但是,在某些情况下,继承是有意的,因为像 stack 这样的容器适配器具有受保护的成员 c (它们适应的底层容器),并且它几乎只能从派生类访问实例
而不是继承或组合,考虑编写自由函数,它接受迭代器对或容器引用,并对其进行操作。实际上所有&lt;算法&gt;就是这样的一个例子;和 make_heap , pop_heap 和 push_heap ,尤其是使用自由函数而不是特定于域的容器的示例。
因此,请为您的数据类型使用容器类,并仍然为您的特定于域的逻辑调用自由函数。但是你仍然可以使用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
请注意,value_type和allocator可以在不影响使用typedef的后续代码的情况下进行更改,甚至容器也可以从 deque 更改为 vector 。
答案 1 :(得分:30)
你可以结合使用私有继承和'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;
}
答案 2 :(得分:12)
正如大家已经说过的那样,STL容器没有虚拟析构函数,因此从它们继承是最不安全的。我一直认为使用模板的泛型编程是一种不同的OO风格 - 一种没有继承。算法定义了它们所需的接口。它与静态语言一样接近Duck Typing。
无论如何,我确实有一些东西要添加到讨论中。我之前创建自己的模板特化的方法是定义类如下的类作为基类。
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;
}
};
这些类公开与STL容器相同的接口。我确实喜欢将修改和非修改操作分成不同的基类的效果。这对const正确性有很好的影响。一个缺点是,如果要将这些与关联容器一起使用,则必须扩展接口。我没有遇到这种需要。
答案 3 :(得分:5)
在这种情况下,继承是一个坏主意:STL容器没有虚拟析构函数,因此您可能遇到内存泄漏(此外,它表明STL容器首先不应该继承)。 / p>
如果您只需要添加一些功能,可以在全局方法中声明它,或者使用容器成员指针/引用声明它。这个过程不允许你隐藏方法:如果那是你真正想要的,那么没有其他选择,然后重新声明整个实现。
答案 4 :(得分:2)
除了虚拟dtors之外,继承与包含的决定应该是基于您正在创建的类的设计决策。你永远不应该继承容器功能,因为它比容纳容器更容易,并添加一些看似简单的包装器的添加和删除功能,除非你可以肯定地说你正在创建的类是一种容器。例如,课堂课程通常包含学生对象,但课堂不是大多数目的的学生列表,因此您不应该从列表中继承。
答案 5 :(得分:1)
这样做更容易:
typedef std::vector<MyObject> MyContainer;
答案 6 :(得分:1)
无论如何,转发方法将被内联。你不会以这种方式获得更好的表现。事实上,你的表现可能会更差。