【问题标题】:C++ discourages base class of collection - is there anyway to fake it?C++ 不鼓励收集基类 - 无论如何要伪造它吗?
【发布时间】:2017-03-09 07:32:36
【问题描述】:

(Java)Collection in C++ 没有类似的概念。

我可以理解原因,但我想知道有没有什么方法可以优雅地伪造

示例

我已经实现了许多自定义Collections。
它们都有 Iterator 可以正常工作,类似于 std::vectorstd::unordered_set 等。

他们是MyArray<T>MyBinaryTree<T>MySet<T>

在这里,我将显示一个工作代码,显示我想要伪造它的位置。

假设我有 2 个级别的程序:库和用户。
它只做一件事——User 命令Library 吃掉一个桶里的所有Orange*s。

图书馆.h

class Library{
    public: static void eatAll(const MyArray<Orange*>& bucket);
};

库.cpp

#include "Orange.h"
void Library::eatAll(const MyArray<Orange*>& bucket){
    for(auto orange:bucket){
        orange->eaten();
    }
}

User.h

MyArray<Orange*> bucket;
Library::eatAll(bucket);    

没关系。

现在,我希望Library::eatAll 也支持MyBinaryTree&lt;Orange*&gt;,我有一些不太理想的方法如下。

我的糟糕解决方案

1。 Java方式

  • 使MyBinaryTree&lt;T&gt;MyArray&lt;Orange*&gt;(及其迭代器)继承自一个新类Collection&lt;T&gt;(和CollectionIterator&lt;T&gt;)。
  • 将签名更改为Library::eatAll(const Collection&lt;T&gt;&amp;)

缺点:Collection&lt;T&gt; 中某些功能的“虚拟”性能损失。

2。模板 v1

//Library.h
template<class T> void eatAll(const T&t ){
    for(auto orange : t){
        orange->eaten();
    }
}
  • 使eatAll成为模板函数

缺点:eatAll的实现必须在header中。
我必须在Library.h#include orange.h
有时候,我真的只想转发声明。

3。模板 v2

//Library.h
template<class T> void eatAll(const T&t ){
    for(auto orange : t){
        eatAnOrange(orange)
    }
}
private: void eatAnOrange(Orange* orange){
    //below implementation is inside Library.cpp
    orange->eaten();
}
  • 创建中间人函数eatAnOrange

缺点:

  • 代码可读性差,不简洁,导致一点可维护性问题。
  • 如果有很多其他功能,例如squeezeAll(),我可能需要创建很多中间人功能,例如squeezeAnOrange()

4。运算符=()

通过隐式构造函数在 3 个集合类之间创建转换器。
缺点:创建新的集合实例会降低性能。

//Here is what it will do, internally (roughly speaking)
MyBinaryTree<Orange*> bucket;
Library::eatAll(MyArray<Orange*>(bucket)); 

我认为我的解决方案并不优雅。
有没有没有上述缺点的解决方案?

编辑:
当前的两个答案都比我的方法优雅(谢谢!),但仍然有缺点:-
- Oliv 需要 #include "orange.h"User.h
- Richard Hodges 有虚函数调用。

【问题讨论】:

  • 您可能可以键入擦除集合并拥有一个接受公共基类的非模板函数,但这会引入虚拟调用(这就是 Java 所具有的,作为接口的基类) .否则,将您的函数设为函数模板,其中 T 是容器,而不是包含的类型。
  • @skypjack 感谢您提供替代解决方案,skypack! ...T 是容器 = 我的“模板方式 1”吗?
  • Container Concept 最接近您的想法。
  • @R Sahu 谢谢,新的“概念”涵盖了非常广泛的领域!您是否在实践中使用过 Concept(C++ 实验性功能),即是否已经亲自采用?
  • 您应该记住的模型是,虽然 java 在运行时多态性(通过虚函数实现)方面很重要,但 C++ 在编译时间方面很重要多态性(通过模板实现)。

标签: c++ c++11 templates containers


【解决方案1】:

在 C++ 中,使用迭代器设计模式遍历集合。整个 STL 都是围绕这个概念设计的。它可能适合您的需求:

您可以将eatAll 定义为接受两个迭代器的函数:

template<class Iterator,class Sentinel>
void eatAll(Iterator it, Sentinel s){
    for (;it!=s;++it)
      it->eaten();
}

或者范围之类算法接口:

template<class Range>
void eatAll(Range& r){
    for (auto& v:r)
      v.eaten();
}

您必须将二叉树定义为一个范围(它必须实现begin()end())。希望树是一种可以线性化的图。然后所有的智能工作都将进入迭代器实现!

【讨论】:

  • 橙色类型和集合类型都隐藏在模板/自动中......好技巧......我会尝尝。谢谢!唯一的缺点是IDE会头晕,所以上下文提示(函数内的ctrl+空格)对橙色不起作用。
  • @javaLover 这种设计模式在 STL 和我读过的大多数 C++ 程序中确实无处不在。您可以对此充满信心。
  • 哦,我刚刚发现了另一个缺点。 .... 现在,User.h 将必须是 #include "orange.h",如果我不将它包含在 Library.h 中。 .... 正确的? .... 这有点难以管理 + 编译时间较慢。 ... 是的,您的解决方案仍然比我的 Template v1/v2 方法更好。
  • 1.这是模板元编程的问题。现在编译器真的很快(至少如果你并行编译make -j8),你不应该担心这个问题从滑稽的日子......(2010 年之前)。 2. 如果是我的解决方案,我真的会很自豪,但是这个解决方案是在我出生之前发明的!数十年来,数以百万计的程序员和架构师在 C++ 中大量使用它,它的好处已被实验证明!你应该采纳它。
【解决方案2】:

如果你想要它真正多态,那么我们必须处理两件事:

  1. 容器的实际类型

  2. 取消引用映射的结果是一对包含键和值引用的事实。

我的观点是,这个问题的答案不是从容器派生,这是有限制的,而是创建一个多态的“值迭代器”,它对所有迭代器进行建模并正确提取它们的值。

那么我们可以这样写代码:

int main()
{

    std::vector<Orange> vo {
            Orange(), Orange()
    };

    std::map<int, Orange> mio {
            { 1, Orange() },
            { 2, Orange() },
            { 3, Orange() }
    };

    std::cout << "vector:\n";
    auto first = makePolymorphicValueIterator(vo.begin());
    auto last = makePolymorphicValueIterator(vo.end());
    do_orange_things(first, last);

    std::cout << "\nmap:\n";
    first = makePolymorphicValueIterator(mio.begin());
    last = makePolymorphicValueIterator(mio.end());
    do_orange_things(first, last);
}

要得到这个:

vector:
Orange
Orange

map:
Orange
Orange
Orange

这是一个最小的、完整的实现:

#include <typeinfo>
#include <memory>
#include <iostream>
#include <vector>
#include <map>
#include <iterator>

// define an orange
struct Orange {
};

// a meta-function to get the type of the value of some iterated value_type    
template<class ValueType> struct type_of_value
{
    using type = ValueType;
};

// specialise it for maps and unordered maps
template<class K, class V> struct type_of_value<std::pair<K, V>>
{
    using type = V;
};

template<class ValueType> using type_of_value_t = typename type_of_value<ValueType>::type;

// function to extract a value from an instance of a value_type    
template<class ValueType> struct value_extractor
{
    template<class V>
    auto& operator()(V&& v) const {
        return v;
    }
};

// specialised for maps    
template<class K, class V> struct value_extractor<std::pair<K, V>>
{
    template<class Arg>
    auto& operator()(Arg&& v) const {
        return std::get<1>(v);
    }
};

template<class Iter>
auto extract_value(Iter const& iter) ->  auto&
{
    using value_type = typename std::iterator_traits<Iter>::value_type;
    auto e = value_extractor<value_type> {};
    return e(*iter);
}

// a polymorphic (forward only at the moment) iterator
// which delivers the value (in the case of maps) or the element (every other container)
template<class ValueType>
struct PolymorphicValueIterator {

    using value_type = type_of_value_t<ValueType>;

private:
    struct iterator_details {
        std::type_info const &type;
        void *address;
    };

    struct concept {

        virtual std::unique_ptr<concept> clone() const = 0;

        virtual value_type& invoke_deref() const = 0;

        virtual void invoke_next(std::size_t distance = 1) = 0;

        virtual iterator_details get_details() = 0;

        virtual bool is_equal(const iterator_details &other) const = 0;

        virtual ~concept() = default;

    };

    template<class Iter>
    struct model final : concept {

        model(Iter iter)
                : iter_(iter)
        {}

        std::unique_ptr<concept> clone() const override
        {
            return std::make_unique<model>(iter_);
        }


        virtual value_type& invoke_deref() const override {
            return extract_value(iter_);
        }

        void invoke_next(std::size_t distance = 1) override
        {
            iter_ = std::next(iter_, distance);
        }

        iterator_details get_details() override {
            return {
                    typeid(Iter),
                    std::addressof(iter_)
            };
        }

        bool is_equal(const iterator_details &other) const override {
            if (typeid(Iter) != other.type) {
                return false;
            }
            auto pother = reinterpret_cast<Iter const*>(other.address);
            Iter const& iother = *pother;
            return iter_ == iother;
        }

        Iter iter_;
    };


    std::unique_ptr<concept> concept_ptr_;

public:
    bool operator==(PolymorphicValueIterator const &r) const {
        return concept_ptr_->is_equal(r.concept_ptr_->get_details());
    }

    bool operator!=(PolymorphicValueIterator const &r) const {
        return not concept_ptr_->is_equal(r.concept_ptr_->get_details());
    }

    PolymorphicValueIterator &operator++() {
        concept_ptr_->invoke_next(1);
        return *this;
    }

    value_type& operator*() const {
        return concept_ptr_->invoke_deref();
    }

    template<class Iter>
    PolymorphicValueIterator(Iter iter)
    {
        concept_ptr_ = std::make_unique<model<Iter>>(iter);
    }

    PolymorphicValueIterator(PolymorphicValueIterator const& r)
            : concept_ptr_(r.concept_ptr_->clone())
    {}

    PolymorphicValueIterator& operator=(PolymorphicValueIterator const& r)
    {
        concept_ptr_ = r.concept_ptr_->clone();
        return *this;
    }

};

template<class Iter>
auto makePolymorphicValueIterator(Iter iter)
{
    using iter_value_type = typename std::iterator_traits<Iter>::value_type;
    using value_type = type_of_value_t<iter_value_type>;
    return PolymorphicValueIterator<value_type>(iter);
}

// a test
void do_orange_things(PolymorphicValueIterator<Orange> first, PolymorphicValueIterator<Orange> last)
{
    while(first != last) {
        std::cout << "Orange\n";
        ++first;
    }
}

int main()
{

    std::vector<Orange> vo {
            Orange(), Orange()
    };

    std::map<int, Orange> mio {
            { 1, Orange() },
            { 2, Orange() },
            { 3, Orange() }
    };

    std::cout << "vector:\n";
    auto first = makePolymorphicValueIterator(vo.begin());
    auto last = makePolymorphicValueIterator(vo.end());
    do_orange_things(first, last);

    std::cout << "\nmap:\n";
    first = makePolymorphicValueIterator(mio.begin());
    last = makePolymorphicValueIterator(mio.end());
    do_orange_things(first, last);
}

【讨论】:

  • 在这种方法中,我将遭受与我的第一种方法(Java 方式)相同的“虚拟”成本,对吧? ....(感谢您花时间编写完整的代码。)
  • @javaLover 任何多态的东西都会遭受(通常是想象的)“虚拟成本”。另一个答案提供了通用编程解决方案,在大多数情况下会更有效,但不提供多态性。付你的钱,做你的选择...... :)
  • @javaLover 无论您采用哪种方法,请注意,两个答案都(强烈)表明没有必要派生一个通用的“容器”。如果您想这样做,那么我建议使用“容器视图”来存储对容器的引用。无论您采用哪种方式,您仍然会在迭代器上进行虚拟调用。仅当您在紧密循环中迭代数百万次且每次迭代的逻辑很少时,这才有意义。
  • @javaLover 模板元编程和多态就像一枚硬币的两面。使用元编程,您将获得最快的程序,而它们不是同一模板的两个不同实例。但在某些时候你会得到所谓的代码膨胀。在这一点上,类似这个答案中提出的类型擦除技术变得有用。
  • @javaLover 如果您对 C++ 技术的本体感兴趣,PolymorphicValueIterator 实现了一种称为“值类型多态性”的技术,这是一种“类型擦除”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-04-30
  • 2017-08-13
  • 2019-02-07
  • 1970-01-01
  • 2019-08-22
  • 1970-01-01
  • 2011-04-10
相关资源
最近更新 更多