11

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

假设我们有一个 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<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;
}

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

另外, inmoo()是 const,我可以调用joo()which 将修改xs的数据。不是我想要的。在这里,我更喜欢编译错误。

完整代码可在ideone.com 获得

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

谢谢你。:-)

4

4 回答 4

6

我认为通常的答案是,对于类模板X<T>,anyX<const T>可以是专门的,因此编译器不允许简单地假设它可以转换指针或 to 的引用,X<T>并且X<const T>没有通用的方法来表示这两个实际上是可转换的. 但后来我想:等等,有一种说法X<T> 是 IS A X<const T>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_; }
};

活生生的例子

于 2013-10-27T14:56:19.967 回答
1

日浦,

我试图从 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));

+模组+

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

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

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

BR,Marek Szews

于 2013-12-19T00:56:56.873 回答
1

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

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

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

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

解决这个问题的一种方法是开始工作范围视图:非拥有视图进入其他容器。a 的非拥有T const*迭代器视图std::vector<T *>是可能的,并且可以为您提供所需的接口。

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

于 2013-10-28T01:25:57.883 回答
0

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

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

代码可能可以改进,但主要思想是在现有容器(例如)上有两个模板类View和,该容器具有用于迭代底层容器的and方法。ConstViewstd::vector<T>begin()end()

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

因为我不喜欢指针,所以我使用模板专业化来隐藏std::shared_ptr:容器上的视图std::shared_ptr<T>不需要额外的取消引用。(我还没有为原始指针实现它,因为我不使用它们。)

这是我的观点的一个基本示例。

于 2013-11-17T09:40:28.360 回答