此代码演示了我试图解决的问题:
#include <map>
class Point
{
public:
float m_x;
float m_y;
};
typedef std::set<Point *> PointSet;
typedef std::set<const Point * const> ConstPointSet;
float GetMinimumRange(const ConstPointSet &pointSet)
{
float minimumRange(0.0f);
// find the smallest distance between any pair of points in the set
return minimumRange;
}
float GetMinimumRangeWrong(const PointSet &pointSet)
{
PointSet::iterator first(pointSet.begin());
Point * point(*first);
point->m_x = 42.0f; // I want to prevent this
return 0.0f;
}
class PointSet_
{
public:
std::set<Point *> m_pointSet;
float GetMinumumRange() const
{
PointSet::iterator first(m_pointSet.begin());
Point * point(*first);
point->m_x = 42.0f; // I want to prevent this
return 0.0f;
}
};
void test()
{
PointSet myPointSet;
// Add some points to my set
// This fails because the compiler states it can't convert from PointSet to ConstPointSet.
//float minimumRange1(GetMinimumRange(myPointSet));
// reinterpret_cast<> is the only cast that works here, const_cast fails with the same
// complaint as the line above generates
ConstPointSet *myConstPointSet(reinterpret_cast<ConstPointSet *>(&myPointSet));
float minimumRange1(GetMinimumRange(*myConstPointSet));
float minimumRange2(GetMinimumRangeWrong(myPointSet));
}
我想创建一个采用PointSet
的例程,评估集合中任意一对Point
之间的最小范围,但它保证它不会修改{ {1}}以任何方式传递给它。它无法修改任何引用的PointSet
的成员,它无法自行更改指针,也无法在集合中添加或删除成员
问题是编译器正确地将Point
和PointSet
视为不同的类型,因为内部类型的ConstPointSet
限定符不同,因此拒绝在它们之间进行转换,甚至虽然我只添加了const
个限定词。
我尝试创建一个包含const
的类,并创建一个const成员函数,但即使在那里它也允许修改一个内部PointSet
。至少MSVC会在没有投诉的情况下编译。我承认我对此感到非常惊讶。
我发现有效的唯一方法是使用Point
将指向reinterpret_cast<>
的指针转换为指向PointSet
的指针。该标准确实注意到ConstPointSet
可用于添加reinterpret_cast<>
限定符,但在这种情况下是否适用?
如果没有,有什么办法可以做我想要的吗?我意识到可以使用良好的代码规则来确保const
不会修改传递的GetMinimumRange()
,但我希望在那里获得PointSet
个限定符有两个原因。
他们会确保如果有人修改const
,他们就无法修改GetMinimumRange()
。
它将允许编译器优化对PointSet
的调用。在没有GetMinimumRange()
限定符的情况下,在调用站点上不能对可能在调用中缓存的值进行任何假设,从而可能导致冗余的数据提取。
答案 0 :(得分:4)
没有直接的方法,因为const
ness不会通过指针传播。在const PointSet
中,指针本身是const
,而不是它们指向的对象。而且,就像您发现的那样,const Point *
与Point *
的类型不同,因此std::set<const Point *>
与std::set<Point *>
的类型不同。
我不喜欢STL结构的reinterpret_cast
。这对我来说很可怕。 STL根据模板参数的类型进行各种优化。 std::vector<bool>
是一个极端的例子。您认为std::set<T *>
和std::set<const T *>
的布局是相同的,因为它们都是指针,但我不会这样认为,直到我在标准中读到它。
如果这是我自己编写的一个结构,而且我可以很容易地证明演员会工作,那就不那么可怕但仍然很难看。
您可以编写一个包装类,该类包含对std::set<Point *>
的引用,但只允许const
通过迭代器访问其指向的Points
。如果指针保证不是非null
,则迭代器可以直接取消引用点。我把它作为模板写在这里:
template <typename T>
class PointerSetViewer
{
public:
PointerSetViewer(std::set<T *> const &set) : set(set) {}
struct iterator : public std::iterator<std::forward_iterator_tag, T const>
{
iterator(typename std::set<T *>::const_iterator it) : it(it) {}
T const &operator*() const { return **it; }
T const *operator->() const { return *it; }
iterator &operator++() { ++it; return *this; }
bool operator==(iterator other) { return it == other.it; }
bool operator!=(iterator other) { return it != other.it; }
private:
typename std::set<T *>::const_iterator it;
};
iterator begin() { return iterator(set.cbegin()); }
iterator end() { return iterator(set.cend()); }
private:
std::set<T *> const &set;
};
它很笨重,但它可以在不做任何冒险的情况下实现您的目标:
float GetMinimumRangeWrong(PointerSetViewer<Point> &pointSet)
{
PointerSetViewer<Point>::iterator first(pointSet.begin());
first->m_x = 42.0f; // does not compile
}
此外,如果您使用的是C ++ 11,则可以获得一些不错的基于范围的for
循环:
template <typename T>
PointerSetViewer<T> view_set(std::set<T *> const &set) {
return PointerSetViewer<T>(set);
}
for (Point const &p : view_set(myPointSet)) {
// whatever...
}
巴洛克?是的,但是如果一个巴洛克式的库代码允许你编写100个漂亮的应用程序代码并进行更好的类型检查,那么它可能是值得的。
答案 1 :(得分:1)
编辑:这不适用于设置。正如评论中所指出的,非const set
被定义为持有const T
,因此实际上我们无能为力。
在这个阶段,我没有看到一个可行的解决方案,除了让PointSet_
实际正确地包裹set
,即让set
私有并且在你的公共职能中要小心。
这是我提出的解决方案;使set
包含一个小包装器,它将自己的const
- 传播到指针上。
我原以为会有一个预先存在的类来执行此操作,但似乎没有std
个智能指针类。
#include <iostream>
#include <set>
template<typename T>
struct qualifier_ptr
{
T *operator->() { return ptr; }
T const *operator->() const { return ptr; }
operator T*() { return ptr; }
operator T const*() const { return ptr; }
qualifier_ptr(T *p): ptr(p) {}
private:
T *ptr;
};
struct Point
{
float m_x;
float m_y;
};
struct PointSet
{
typedef std::set< qualifier_ptr<Point> > SetType;
SetType points;
float foo() const
{
//Point *p = *points.begin(); // error
Point const *p = *points.begin(); // OK
return 0;
}
};
int main()
{
PointSet ps;
PointSet const &cps = ps;
ps.foo(); // OK
cps.foo(); // OK
}
我通常不喜欢使用转换运算符,但这似乎是合适的。
答案 2 :(得分:0)
正如你在评论中所说,每个会话只建立一次这个集合,我建议你只需创建一个副本来创建ConstPointerSet:
void test()
{
PointSet myPointSet;
// Add some points to my set
ConstPointSet myConstPointSet{ begin(myPointSet), end(myPointSet) };
float minimumRange1(GetMinimumRange(myConstPointSet));
}
或将其包装成函数:
ConstPointSet toConst(const PointSet& pSet){
return ConstPointSet{ cbegin(pSet), cend(pSet) };
}
如果您不需要集合的语义,我建议您使用std::vector
,这样可以更有效地复制或遍历。