【问题标题】:Efficient alternatives for exposing a Collection公开集合的有效替代方案
【发布时间】:2010-09-07 20:45:42
【问题描述】:

在 C++ 中,从性能和数据完整性的角度来看,我有哪些方法可以公开集合?

我的问题是我想将内部数据列表返回给调用者,但我不想生成副本。 Thant 让我要么返回对列表的引用,要么返回指向列表的指针。但是,我并不热衷于让调用者更改数据,我只是想让它读取数据。

  • 我必须在性能和数据完整性之间做出选择吗?
  • 如果是这样,一般来说是采用一种方式更好还是特定于情况?
  • 还有其他选择吗?

【问题讨论】:

    标签: c++ performance collections data-integrity


    【解决方案1】:

    很多时候调用者想要访问只是为了遍历集合。从 Ruby 的书中取出一页,让迭代成为您班级的私人方面。

    #include <algorithm>
    #include <boost/function.hpp>
    
    class Blah
    {
      public:
         void for_each_data(const std::function<void(const mydata&)>& f) const
         {
             std::for_each(myPreciousData.begin(), myPreciousData.end(), f);
         }
    
      private:
         typedef std::vector<mydata> mydata_collection;
         mydata_collection  myPreciousData;
    };
    

    使用这种方法,您不会暴露任何有关您的内部结构的信息,即您甚至 拥有一个集合。

    【讨论】:

    • 我知道这是一个旧答案,但我现在用 std::function 这样做:void for_each_data(const std::function&lt;void(const mydata&amp;)&gt;&amp; f) const;
    • @JoBates 我不再每天使用 C++,请随时编辑我的答案以使其更新!
    【解决方案2】:

    RichQ's answer 是一种合理的技术,如果您使用的是数组、向量等。

    如果您使用的集合没有按序数值索引...或者认为您可能需要在不久的将来的某个时候...那么您可能需要考虑公开您自己的迭代器类型和关联的begin()/end() 方法:

    class Blah
    {
    public:
       typedef std::vector<mydata> mydata_collection;
       typedef myDataCollection::const_iterator mydata_const_iterator;
    
       // ...
    
       mydata_const_iterator data_begin() const 
          { return myPreciousData.begin(); }
       mydata_const_iterator data_end() const 
          { return myPreciousData.end(); }
    
    private:
       mydata_collection  myPreciousData;
    };
    

    ...然后您可以正常使用:

    Blah blah;
    for (Blah::mydata_const_iterator itr = blah.data_begin();
       itr != blah.data_end();
       ++itr)
    {
       // ...
    }
    

    【讨论】:

      【解决方案3】:

      也许是这样的?

      const std::vector<mydata>& getData()
      {
        return _myPrivateData;
      }
      

      这里的好处是它非常、非常简单,并且与您使用 C++ 一样安全。你可以像 RobQ 建议的那样施放这个,但如果你不复制的话,你无能为力。在这里,您必须使用const_cast,如果您正在寻找它,很容易发现它。

      迭代器,或者,可能会得到几乎相同的东西,但它更复杂。在这里使用迭代器的唯一额外好处(我能想到的)是您可以有更好的封装。

      【讨论】:

        【解决方案4】:

        只有当底层集合的内容不随时间变化时,使用 const 引用或共享指针才会有所帮助。

        考虑您的设计。调用者真的需要查看内部数组吗?你能重组代码,让调用者告诉对象如何处理数组吗?例如,如果调用者打算搜索数组,所有者对象可以这样做吗?

        您可以将结果向量的引用传递给函数。在某些编译器上可能会导致代码稍微快一些。

        我建议先尝试重新设计,然后使用干净的解决方案,然后再优化性能(如有必要)。

        【讨论】:

          【解决方案5】:

          @Shog9 和@RichQ 的解决方案的一个优点是它们将客户端与集合实现分离。

          如果您决定将您的收藏类型更改为其他类型,您的客户仍然可以工作。

          【讨论】:

            【解决方案6】:

            您想要的是只读访问而不复制整个数据块。你有几个选择。

            首先,您可以只返回一个对您的数据容器的 const 引用,就像上面建议的那样:

            const std::vector<T>& getData() { return mData; }
            

            这有具体的缺点:你不能改变你在内部存储数据的方式而不改变你的类的接口。

            其次,您可以返回指向实际数据的 const 指针:

            const T* getDataAt(size_t index)
            {
               return &mData[index];
            }
            

            这更好一点,但也需要您提供 getNumItems 调用,并防止索引越界。此外,您的指针的 const-ness 很容易被抛弃,您的数据现在是可读写的。

            另一种选择是提供一对迭代器,这有点复杂。这具有与指针相同的优点,并且不需要(必然)需要提供 getNumItems 调用,并且需要更多的工作来去除迭代器的 const 特性。

            管理此问题的最简单方法可能是使用 Boost Range:

            typedef vector<T>::const_iterator range_iterator_type;
            boost::iterator_range< range_iterator_type >& getDataRange()
            {
                return boost::iterator_range(mData.begin(), mData.end());
            }
            

            这具有范围可组合、可过滤等优点,正如您在 website 上看到的那样。

            【讨论】:

              【解决方案7】:

              使用 const 是一个合理的选择。 您可能还希望查看 boost C++ 库的共享指针实现。它提供了指针的优点,即您可能需要将共享指针返回到引用不允许的“null”。

              http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm

              在您的情况下,您可以将共享指针的类型设为 const 以禁止写入。

              【讨论】:

                【解决方案8】:

                如果您有一个 std::list 的普通旧数据(.NET 将其称为“值类型”),那么返回对该列表的 const 引用就可以了(忽略诸如 const_cast 之类的邪恶事物)

                如果你有一个std::list 的指针(或boost::shared_ptr's),那只会阻止你修改集合,而不是集合中的项目。我的 C++ 太生疏了,现在无法告诉你答案:-(

                【讨论】:

                  【解决方案9】:

                  我建议按照EnumChildWindows 的方式使用回调。您必须找到一些方法来防止用户更改您的数据。也许使用const 指针/引用。

                  另一方面,您可以将每个元素的副本传递给回调函数,每次都覆盖该副本。 (您不想生成整个集合的副本。我只是建议一次制作一个元素的副本。这不会占用太多时间/内存)。

                  MyClass tmp;
                  for(int i = 0; i < n; i++){
                      tmp = elements[i];
                      callback(tmp);
                  }
                  

                  【讨论】:

                    【解决方案10】:

                    以下两篇文章详细阐述了封装容器类所涉及的一些问题以及封装容器类的必要性。尽管它们没有提供一个完整的可行解决方案,但它们基本上导致了与 Shog9 给出的相同方法。

                    第 1 部分:Encapsulation and Vampires
                    第 2 部分(现在需要免费注册才能阅读):Train Wreck Spotting
                    凯夫林·亨尼(Kevlin Henney)

                    【讨论】:

                    • 仅供参考,您发布的链接已失效。
                    • @MM。感谢您的通知,现已修复。
                    猜你喜欢
                    • 2011-09-24
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-10-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多