代码之家  ›  专栏  ›  技术社区  ›  Emile Cormier

从STL容器继承实现而不是委托可以吗?

  •  69
  • Emile Cormier  · 技术社区  · 15 年前

    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使用MyContainer<&燃气轮机;指针:

    // non_api_header_file.h
    namespace detail
    {
       typedef std::vector<MyObject> MyObjectBase;
    }
    
    // api_header_file.h
    class MyContainer : public detail::MyObjectBase
    {
       // ...
    };
    

    boost库似乎一直在做这些事情。

    其中一个建议是使用自由函数。我将在这里显示为伪代码:

    typedef std::vector<MyObject> MyCollection;
    void specialCollectionInitializer(MyCollection& c, arguments...);
    result specialCollectionFunction(const MyCollection& c);
    etc...
    

    更糟糕的做法是:

    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.
    }
    
    7 回复  |  直到 5 年前
        1
  •  76
  •   stilltracy    8 年前

    风险在于通过指向基类的指针进行释放 删除 删除[] , 地图 一串 ,等等)没有虚拟DTR,仅使用指向这些类的指针不可能正确清理它们:

    struct BadExample : vector<int> {};
    int main() {
      vector<int>* p = new BadExample();
      delete p; // this is Undefined Behavior
      return 0;
    }
    

    如果 一串 堆栈 拥有受保护的成员 C (它们适应的底层容器),并且它几乎只能从派生类实例访问。

    而不是继承或组合, 考虑编写自由函数 它接受迭代器对或容器引用,并对其进行操作。几乎所有<算法>这是一个例子;和 ,及 推送堆

    因此,对数据类型使用容器类,并且仍然为特定于域的逻辑调用自由函数。但是,您仍然可以使用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
    

    请注意,type和分配器的值可以更改,而不会影响以后使用typedef的代码,甚至容器也可以从 德克 .

        2
  •  37
  •   Ben    15 年前

    您可以将私有继承和'using'关键字结合起来解决上面提到的大多数问题:私有继承是'is implemented 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;
    }
    
        3
  •  15
  •   D.Shawley    15 年前

    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正确性有很好的影响。一个缺点是,如果要将接口与关联容器一起使用,则必须扩展接口。不过我还没有遇到这种需要。

        4
  •  5
  •   stijn    15 年前

    在这种情况下,继承是一个坏主意:STL容器没有虚拟析构函数,因此您可能会遇到内存泄漏(另外,这表明STL容器本来就不应该被继承)。

    如果您只需要添加一些功能,您可以在全局方法中声明它,或者在一个具有容器成员指针/引用的轻量级类中声明它。这不允许您隐藏方法:如果这是您真正想要的,那么在重新说明整个实现之后就没有其他选择了。

        5
  •  4
  •   Jherico    15 年前

    除非 您可以明确地说,您正在创建的类是一种容器。例如,教室类通常包含学生对象,但教室在大多数情况下不是学生列表,所以您不应该从列表继承。

        6
  •  1
  •   Loki Astari    15 年前

    这样做更容易:

    typedef std::vector<MyObject> MyContainer;
    
        7
  •  1
  •   Charles Eli Cheese    15 年前

    无论如何,转发方法将被内联。这样您将无法获得更好的性能。事实上,您可能会获得更差的性能。