shared_ptr <t>到shared_ptr <t const =“”>和vector <t>到vector <t const =“”> </t> </t> </t> </t>

时间:2013-10-27 09:36:16

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

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

我们假设我们有一个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个关键字,因为它不是我面临的实际问题。)

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

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<XPtr>。 (也可能是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(),即const,我可以致电joo(),这将修改xs的数据。不是我想要的。在这里,我更喜欢编译错误。

完整代码位于ideone.com

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

谢谢。 : - )

4 个答案:

答案 0 :(得分:6)

我认为通常的答案是,对于类模板X<T>,任何X<const T>都可以是专用的,因此编译器不允许简单地假设它可以转换{{1的指针或引用到X<T>并且没有通用的方式来表达这两个实际上是可转换的。但是我会说:等等,有一种方法可以说X<const T> 是A X<T> IS A 通过继承表达。

虽然这对X<const T>或标准容器没有帮助,但是在实现自己的类时可能需要使用这种技术。事实上,我想知道std::shared_ptr是否可以/应该改进容器以支持这一点。任何人都可以看到这个问题吗?

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

std::shared_ptr

Live example

答案 1 :(得分:1)

您想要做的事情存在根本问题。

std::vector<T const*>不是std::vector<T*>的限制,包含智能指针及其vector版本的const也是如此。

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

现在,技术上std::vector<T const*> conststd::vector<T>的限制,但不支持。

解决这个问题的方法是开始使用范围视图:非拥有其他容器的视图。可以在T const*中拥有非std::vector<T *>迭代器视图,并且可以为您提供所需的界面。

boost::range可以为您做样板,但编写自己的contiguous_range_view<T>random_range_view<RandomAccessIterator>并不难。如果您希望自动检测迭代器类别并启用基于此功能的功能,这就变得很奇怪,这就是boost::range包含更多代码的原因。

答案 2 :(得分:1)

Hiura,

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

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, Marek Szews

答案 3 :(得分:0)

根据评论和答案,我最终创建了容器视图。

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

代码可能会有所改进,但主要思想是在现有容器(例如View)上有两个模板类ConstViewstd::vector<T>,其中{{1}和} begin()方法迭代底层容器。

通过一点继承(end()View),它可以在需要时将read-write转换为只读视图,而无需额外的代码。

由于我不喜欢指针,我使用模板特化来隐藏ConstViewstd::shared_ptr容器上的视图不需要额外的解除引用。 (我还没有为原始指针实现它,因为我不使用它们。)

Here is a basic example of my views in action.