这是reinterpret_cast的合法使用,如果不是,我该怎么做?

时间:2015-02-19 22:54:39

标签: c++ pointers const reinterpret-cast

此代码演示了我试图解决的问题:

#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的成员,它无法自行更改指针,也无法在集合中添加或删除成员

问题是编译器正确地将PointPointSet视为不同的类型,因为内部类型的ConstPointSet限定符不同,因此拒绝在它们之间进行转换,甚至虽然我只添加了const个限定词。

我尝试创建一个包含const的类,并创建一个const成员函数,但即使在那里它也允许修改一个内部PointSet。至少MSVC会在没有投诉的情况下编译。我承认我对此感到非常惊讶。

我发现有效的唯一方法是使用Point将指向reinterpret_cast<>的指针转换为指向PointSet的指针。该标准确实注意到ConstPointSet可用于添加reinterpret_cast<>限定符,但在这种情况下是否适用?

如果没有,有什么办法可以做我想要的吗?我意识到可以使用良好的代码规则来确保const不会修改传递的GetMinimumRange(),但我希望在那里获得PointSet个限定符有两个原因。

  1. 他们会确保如果有人修改const,他们就无法修改GetMinimumRange()

  2. 它将允许编译器优化对PointSet的调用。在没有GetMinimumRange()限定符的情况下,在调用站点上不能对可能在调用中缓存的值进行任何假设,从而可能导致冗余的数据提取。

3 个答案:

答案 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,这样可以更有效地复制或遍历。