我试图为我的软件定义一个好的设计,这意味着要小心对某些变量的读/写访问。在这里,我简化了讨论的程序。希望这对其他人也有帮助。 : - )
我们假设我们有一个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)
,它可以读写p
和hoo(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版本?或者,更一般地说,是否有一种既有效又易于理解的好技术/模式?
谢谢。 : - )
答案 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
答案 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*> const
是std::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
)上有两个模板类ConstView
和std::vector<T>
,其中{{1}和} begin()
方法迭代底层容器。
通过一点继承(end()
是View
),它可以在需要时将read-write转换为只读视图,而无需额外的代码。
由于我不喜欢指针,我使用模板特化来隐藏ConstView
:std::shared_ptr
容器上的视图不需要额外的解除引用。 (我还没有为原始指针实现它,因为我不使用它们。)