【问题标题】:shared_ptr<T> to shared_ptr<T const> and vector<T> to vector<T const>shared_ptr<T> 到 shared_ptr<T const> 和 vector<T> 到 vector<T const>
【发布时间】:2013-10-27 09:36:16
【问题描述】:

我正在尝试为我的软件定义一个好的设计,这意味着要小心对某些变量的读/写访问。在这里,我简化了讨论的程序。希望这对其他人也有帮助。 :-)

假设我们有一个如下的类 X:

class X {
    int x;
public:
    X(int y) : x(y) { }
    void print() const { std::cout << "X::" << x << std::endl; }
    void foo() { ++x; }
};

我们还可以说,将来这个类将被 X1、X2、... 子类化,这可以重新实现 print()foo()。 (为了简单起见,我在这里省略了必需的 virtual 关键字,因为这不是我面临的实际问题。)

由于我们将使用多态,让我们使用(智能)指针并定义一个简单的工厂:

using XPtr = std::shared_ptr<X>;
using ConstXPtr = std::shared_ptr<X const>;

XPtr createX(int x) { return std::make_shared<X>(x); }

到目前为止,一切都很好:我可以定义 goo(p) 可以读写 phoo(p) 只能读取 p

void goo(XPtr p) {
    p->print();
    p->foo();
    p->print();
}

void hoo(ConstXPtr p) {
    p->print();
//    p->foo(); // ERROR :-)
}

调用站点如下所示:

    XPtr p = createX(42);

    goo(p);
    hoo(p);

指向 X (XPtr) 的共享指针会自动转换为其 const 版本 (ConstXPtr)。不错,正是我想要的!

现在麻烦来了:我需要一个异构的 X 集合。我的选择是std::vector&lt;XPtr&gt;。 (也可以是list,为什么不呢。)

我想到的设计如下。我有两个版本的容器:一种对其元素具有读/写访问权限,另一种对其元素具有只读访问权限。

using XsPtr = std::vector<XPtr>;
using ConstXsPtr = std::vector<ConstXPtr>;

我有一个处理这些数据的类:

class E {
    XsPtr xs;
public:
    E() {
        for (auto i : { 2, 3, 5, 7, 11, 13 }) {
            xs.emplace_back(createX(std::move(i)));
        }
    }

    void loo() {
        std::cout << "\n\nloo()" << std::endl;
        ioo(toConst(xs));

        joo(xs);

        ioo(toConst(xs));
    }

    void moo() const {
        std::cout << "\n\nmoo()" << std::endl;
        ioo(toConst(xs));

        joo(xs); // Should not be allowed

        ioo(toConst(xs));
    }
};

ioo()joo() 函数如下:

void ioo(ConstXsPtr xs) {
    for (auto p : xs) {
        p->print();
//        p->foo(); // ERROR :-)
    }
}

void joo(XsPtr xs) {
    for (auto p: xs) {
        p->foo();
    }
}

如您所见,在E::loo()E::moo() 中,我必须对toConst() 进行一些转换:

ConstXsPtr toConst(XsPtr xs) {
    ConstXsPtr cxs(xs.size());
    std::copy(std::begin(xs), std::end(xs), std::begin(cxs));
    return cxs;
}

但这意味着一遍又一遍地复制所有内容.... :-/

另外,在moo() 中,我可以调用joo() 来修改xs 的数据。不是我想要的。这里我宁愿编译错误。

完整代码可在ideone.com获取。

问题是:是否可以做同样的事情但不将向量复制到它的 const 版本?或者,更一般地说,有没有一种既高效又易于理解的好技术/模式?

谢谢。 :-)

【问题讨论】:

  • 获取带有boost::adaptors::transformedconst-view 和适当的函数对象来转换您的共享指针。
  • @Xeo:我已经快速查看了boost::adaptors::transformed,但似乎我必须在某些时候复制一些东西,有点像上面,但语法不同,对吧?如果不是这样,您介意在下面举一个例子吗? :-)
  • 只是一个注释。你的std::move(i) 不会移动任何东西。 move 不动,只是演员阵容。也许它只是从复制你的实际代码到它移动的地方:)

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


【解决方案1】:

我认为通常的答案是,对于类模板X&lt;T&gt;,任何X&lt;const T&gt; 都可以专门化,因此编译器不允许简单地假设它可以将X&lt;T&gt; 的指针或引用转换为X&lt;const T&gt;并且没有一般的方式来表示这两个实际上是可转换的。但后来我想:等等,有办法说X&lt;T&gt; IS A X&lt;const T&gt;IS A 是通过继承来表达的。

虽然这对std::shared_ptr 或标准容器没有帮助,但在您实现自己的类时,您可能希望使用这种技术。事实上,我想知道 std::shared_ptr 和容器是否可以/应该改进以支持这一点。有人能看出这有什么问题吗?

我想到的技术会这样工作:

template< typename T > struct my_ptr : my_ptr< const T >
{
    using my_ptr< const T >::my_ptr;
    T& operator*() const { return *this->p_; }
};

template< typename T > struct my_ptr< const T >
{
protected:
    T* p_;

public:
    explicit my_ptr( T* p )
      : p_(p)
    {
    }

    // just to test nothing is copied
    my_ptr( const my_ptr& p ) = delete;

    ~my_ptr()
    {
        delete p_;
    }

    const T& operator*() const { return *p_; }
};

Live example

【讨论】:

  • 你尝试编译这个吗?
  • @typical 是的,请参阅答案中的“Live example”链接并随意使用它。
  • std::shared_ptr&lt;T&gt; 可以转换为它的常量版本通过 template&lt; class Y &gt; shared_ptr( const shared_ptr&lt;Y&gt;&amp; r );。这就是我的问题的第一部分与goo()hoo() 发生的事情。所以我想我不必为共享指针类重新创建轮子,对吧?
  • @Hiura 现有的shared_ptr&lt;T&gt; 转换shared_ptr&lt;const T&gt;,这意味着创建了一个新的std::shared_ptr&lt;const T&gt; 实例,它具有运行时影响。它是生成的真实代码,包括引用计数。如果您的代码对性能不太重要,则可以使用它。我展示的技术不能直接解决您的问题,因为这意味着需要修复标准和标准库的实现。如果使用它,转换对性能的影响就消失了。
  • 添加.reset(T*) 方法时,根本问题暴露:T const 版本想要允许.reset(T const*),但不能安全地做到这一点。您需要将指针值和内容的读取和写入拆分为不同的类,总共 2x2=4 个类(可能是 3 个,因为您可以合并其中的 2 个,我认为有足够的const_cast)。
【解决方案2】:

你想做的事情有一个根本性的问题。

std::vector&lt;T const*&gt; 不是std::vector&lt;T*&gt; 的限制,同样适用于包含智能指针的vectors 及其const 版本。

具体来说,我可以在第一个容器中存储指向const int foo = 7; 的指针,但不能在第二个容器中存储。 std::vector 既是范围又是容器。这类似于T**T const** 的问题。

现在,从技术上讲,std::vector&lt;T const*&gt; conststd::vector&lt;T&gt; 的限制,但不支持。

解决这个问题的一种方法是开始工作范围视图:非拥有视图进入其他容器。一个非拥有的 T const* 迭代器视图到一个 std::vector&lt;T *&gt; 是可能的,并且可以给你你想要的接口。

boost::range 可以为你做样板,但编写你自己的contiguous_range_view&lt;T&gt;random_range_view&lt;RandomAccessIterator&gt; 并不难。当您想自动检测迭代器类别并启用基于此的功能时,它会变得很花哨,这就是 boost::range 包含更多代码的原因。

【讨论】:

  • 你能详细说明一下那些 boost::range 吗?也许通过展示应该如何编写 ioo 和 joo 来使用它们以及呼叫站点的外观?谢谢。
  • 如果我正确阅读了 boost 文档,std::vector&lt;int const*&gt; 的替换将类似于 boost::iterator_range&lt; std::vector&lt;int*&gt;::const_iterator &gt;
【解决方案3】:

日浦,

我尝试从 repo 编译您的代码,但 g++4.8 返回了一些错误。 main.cpp:97 中的更改以及调用 view::create() 的其余行以 lambda 函数作为第二个参数。 +添加+

auto f_lambda([](view::ConstRef_t<view::ElementType_t<Element>> const& e) { return ((e.getX() % 2) == 0); });

std::function<bool(view::ConstRef_t<view::ElementType_t<Element>>)> f(std::cref(f_lambda));

+mod+

printDocument(view::create(xs, f));

View.hpp:185 还需要额外的操作符,即: +添加+

bool operator==(IteratorBase const& a, IteratorBase const& b)
{
  return a.self == b.self;
}

BR, 马雷克·斯泽斯

【讨论】:

  • 感谢您的提醒,但如果您想遵循 SO 的精神,您应该在 Github 上打开一个问题而不是在这里回答。
【解决方案4】:

根据 cmets 和答案,我最终为容器创建了一个视图。

基本上我定义了新的迭代器。我在这里在 github 上创建了一个项目:mantognini/ContainerView.

代码可能可以改进,但主要思想是在具有begin()end() 方法的现有容器(例如std::vector&lt;T&gt;)上有两个模板类,ViewConstView迭代底层容器。

通过一点继承(ViewConstView),它有助于在需要时将读写转换为只读视图,而无需额外代码。

由于我不喜欢指针,我使用模板特化来隐藏std::shared_ptrstd::shared_ptr&lt;T&gt; 容器上的视图不需要额外的取消引用。 (我还没有为原始指针实现它,因为我不使用它们。)

Here is a basic example of my views in action.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-20
    • 2017-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-21
    • 2018-02-02
    相关资源
    最近更新 更多