【问题标题】:C++11 shared_pointer constness within stl containersstl 容器中的 C++11 shared_pointer 常量
【发布时间】:2013-07-02 20:34:41
【问题描述】:

我有以下问题,不知道有没有更好的解决方法:

class myObj {
public:
    typedef std::shared_ptr<myObj> handle;
    typedef std::shared_ptr<const myObj> const_handle;
    int someMethod() { ... }
    int someConstMethod() const { ... }
};

现在我需要一个容器类,它允许您根据自己的constness 修改或读取myObj 的集合,如下所示:

class myCollection {
public:
    typedef std::list<myObj::handle> objList;
    typedef std::list<myObj::const_handle> const_objList;

    inline objList& modify() { return _obl; }

    // it would be nice to do this, but it won't compile as 
    // objList and const_objList are completely different types
    inline const_objList& read() const { return _obl; } // doh! compile error...

    // returning a const objList won't help either as it would return non-const
    // handles, obviously.

    // so I am forced to do this, which sucks as i have to create a new list and copy
    void read(const_objList &l) {
        std::for_each(
            _obl.begin(), 
            _obl.end(), 
            [&l] (myObj::handle &h) { l.push_back(h); } 
            // ok as handle can be cast to const_handle
        ); // for_each
    }

private:
    objList _obl;
};

所以这个解决方案实际上是作为const myCollection 工作的,它只允许你获取const_handle 的列表,它只允许你调用myObj 的非修改方法(好)。

问题是“read”方法真的很丑(不好)。

另一种方法是以某种方式公开list 方法并返回const_handle 并根据需要进行处理,但这会产生很多开销,特别是如果您想使用比列表更复杂的东西。

有什么想法吗?

【问题讨论】:

  • 可能std::list 不是正确的容器,因为它很少是正确的容器。 std::list 比 std::vector 更好的情况比较少见,更罕见的是它优于std::vector 和std::deque 的情况。
  • 列表的getter/setter怎么样,即提供一个接口来改变列表而不是返回它?如果您只需要更改元素(不删除或插入),迭代器是另一种方式。

标签: c++ c++11 stl constants shared-ptr


【解决方案1】:

指向 T 的指针列表不是指向常量 T 的指针列表。

std::list<std::shared_ptr<int>> a;
std::list<std::shared_ptr<const int>>& ra = a; // illegal but imagine it's not
std::shared_ptr<const int> x = std::make_shared<const int>(42);
ra.push_back(x); // totally legal, right?
++**a.begin(); // oops... just incremented a const int

现在,从概念上讲,指向 T 的指针列表是一个常数列表,但 std::list&lt;std::shared_ptr&lt;T&gt;&gt; 不支持如此深的 const 传播。 const std::list&lt;std::shared_ptr&lt;T&gt;&gt; 包含指向非常量对象的常量指针。

您可以编写自己的list&lt;&gt; 变体或您自己的shared_ptr&lt;&gt; 变体具有此类支持。不过,这可能不会很容易。 const_propagating_shared_ptr 可能是两者中更容易的一个。它必须封装一个std::shared_ptr&lt;T&gt; 对象并将几乎所有内容按原样转发给它。与std::shared_ptr&lt;T&gt; 不同,它将具有单独的const 和非const 版本的operator-&gt;、operator*() 和get()。

【讨论】:

    【解决方案2】:

    鉴于您所说的要完成的任务,我认为您的解决方案并不算太糟糕。想象一下,其他一些代码可能正在修改内部集合,例如添加或删除值。返回集合当前状态的副本对于客户端代码来说是安全的,因为它可以在副本上工作,同时不会有元素被删除的危险。但我离题了,这涉及到线程问题,可能不相关。

    你可以使用更漂亮的:

      inline const_objList read()  const
       { 
           const_objList cl(_obl.begin(), _obl.end());
           return cl; 
       } 
    

    但是,我确实认为您的问题源于混合了两种类型的 constness:集合成员的 constness 与集合本身的 constness。

    我将尝试通过返回所述迭代器的相应 const 和非 const 方法将 const 和非 const 迭代器暴露给内部列表,而不是使用 Modify 和 Read 方法来处理整个列表。

    但这立即引出了一个问题:为什么首先要有myCollection?

    似乎不需要围绕 std::list 创建全新的集合类型,除非您从示例中不可见的其他附加功能中获得了很多众所周知的收益。

    然后,您可以使您添加的功能免费的方法将您的句柄的 std::list 作为输入。不是所有东西都需要一个对象,对对象的操作也不一定是成员方法,除非需要访问私有数据。

    您提到可能使用另一个容器而不是列表。但是你的类不会这样做,除非你有一个模板,其中模板参数可以是 STL 容器之一。 这意味着您应该公开迭代器。 即,如果您预见到更改内部集合类型,您可能希望使myCollection 的公共接口对集合类型透明。否则,每次您改变对内部实现的想法时,客户端都必须重新编译。

    EDIT -----
    

    最后,如果实现迭代器(虽然很有趣且最正确)太多了,为什么不像这篇 SO 帖子中那样使用简单的 getter:

    smart pointer const correctness

    我将引用 Rüdiger Stevens 的最高答案(它假定向量而不是列表):

    template <typename T>
    class MyExample
    {
      private:
        vector<shared_ptr<T> > data;
    
      public:
        shared_ptr<const T> get(int idx) const
        {
          return data[idx];
        }
        shared_ptr<T> get(int idx)
        {
          return data[idx];
        }
        void add(shared_ptr<T> value)
        {
          data.push_back(value);
        }
    };
    

    【讨论】:

      猜你喜欢
      • 2014-07-19
      • 2016-01-13
      • 2012-10-07
      • 2023-03-26
      • 1970-01-01
      • 2018-07-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多