2015-02-19 7 views
2

Этот код демонстрирует проблему, которую я пытаюсь решить:Является ли это законным использованием 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 с в наборе, но она гарантирует что он не будет изменять переданный ему PointSet каким-либо образом. Он не может изменить членов любого ссылочного Point, он не может изменить указатели сами, и не может добавлять или удалять пользователей из множества

Вопрос заключается в том, что компилятор правильно рассматривает PointSet и ConstPointSet, как различные типы, потому что от разницы const отборщиков внутреннего типа, и поэтому отказывается бросить между ними, хотя я только добавляю const отборочные.

Я попытался создать класс, содержащий PointSet, и создав функцию-член const, но даже там, где он допускает модификацию одного из внутренних Point. По крайней мере, MSVC будет компилировать это без жалобы. Признаюсь, я был очень удивлен этим.

Единственный способ, которым я нашел, - это использовать reinterpret_cast<> для преобразования указателя на PointSet в указатель на ConstPointSet. В стандарте отмечается, что reinterpret_cast<> можно использовать для добавления квалификаторов const, но применимо ли это в этом случае?

Если нет, есть ли способ сделать то, что я хочу? Я понимаю, что хорошая дисциплина кода может быть использована для обеспечения того, чтобы GetMinimumRange() не изменял пройденный PointSet, но я хотел бы получить там const отборочных команд по двум причинам.

  1. Они будут гарантировать, что если кто-либо изменяет GetMinimumRange() они не могут заставить его изменить PointSet.

  2. Это позволит компилятору оптимизировать вызов по телефону GetMinimumRange(). В отсутствие квалификаторов const на вызывающем сайте не могут быть сделаны допущения относительно значений, которые могут быть кэшированы по вызову, что может привести к избыточным выборкам данных.

+1

Вам не нужно указывать указатель const, если контейнер является константой. Лично я просто передал копию контейнера и не имел указателей на элементы. Таким образом физически невозможно изменить значение, на которое указывают указатели, и вы можете просто позволить компилятору оптимизировать. – BWG

+0

Это вызывает неопределенное поведение, нарушая строгий псевдоним. Типы, которые являются псевдонимами, являются 'set ' и 'set ', которые не имеют отношения, если 'T' и' U' не идентичны –

+0

@BWG удаление указателей будет работать, но это упрощенный пример кода для демонстрации пороблема. На практике «Point *» на самом деле указывают на Entites в игре. Использование набора <> из них, в отличие от набора указателей, будет иметь некоторые очевидные проблемы с производительностью. – dgnuff

ответ

4

Нет прямого пути, потому что const ness не распространяется через указатели. В const PointSet это сами указатели: const, а не объекты, на которые они указывают. И, как вы обнаружили, const Point * - это другой тип от Point *, поэтому std::set<const Point *> - это другой тип от std::set<Point *>.

Мне не нравится reinterpret_cast структуры STL. Это страшно для меня. 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

Редактировать: это не работает для набора. Как отмечалось в комментариях, неконстантный set определен для хранения const T, поэтому на самом деле мы ничего не можем сделать.

На данном этапе я не вижу жизнеспособное решение, кроме решений PointSet_ фактически обернуть set правильно, то есть set быть частным и быть осторожными в своих публичных функций.


Вот решение, с которым я столкнулся; make set содержит небольшую обертку, которая будет распространять const -ness самого себя на указатель.

Я бы подумал, что будет существовать уже существующий класс, который делает это, но ни один из классов умных указателей 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 
} 

Обычно я не хочу использовать операторы преобразования, но это кажется уместным.

+0

Я сам что-то разработал, чтобы ответить на этот вопрос, но я его отверг. Поскольку 'std :: set :: value_type' является' const T', нет никакой разницы между 'std :: set >' и 'std :: set >'. Таким образом, строка, которую вы прокомментировали с помощью «ошибки», также не работала бы в функции non-const 'foo'. –

+0

@MichaelKarcher argh ... поэтому мое решение работает для 'vector', но не' set'. –

+2

Он также будет работать для значений в 'std :: map'. Поскольку OP содержит заголовок '' вместо заголовка '', может быть, он по-прежнему используется. Ваш код также должен завершиться неудачей в '', потому что вы не предоставляете 'operator <(qualifier_ptr , qualifier_ptr )', no 'std :: less' для' qualifier_ptr 'и не предоставлять аргумент шаблона-компаратора для' std :: set > '. Но вы можете заметить, что только как только вы на самом деле вызываете функцию в наборе, которая должна сравнивать элементы, например 'insert'. –

0

Как указано в комментариях, что множество построен только один раз за сеанс, я предлагаю просто создание ConstPointerSet, сделав копию:

void test() 
{ 
    PointSet myPointSet;  
    // Add some points to my set 
    ConstPointSet myConstPointSet{ begin(myPointSet), end(myPointSet) }; 

    float minimumRange1(GetMinimumRange(myConstPointSet)); 
} 

Или wrapp в функцию:

ConstPointSet toConst(const PointSet& pSet){ 
    return ConstPointSet{ cbegin(pSet), cend(pSet) }; 
} 

Если вам не нужна семантика набора, я бы рекомендовал вместо этого использовать std::vector, что намного эффективнее для копирования или перемещения.

+0

Хороший вопрос о std :: vector <> vs std :: set <>. Вы правы, использование std :: set <>, вероятно, немного отходов, так как оно сортирует сами указатели, что в то же время безвредно и впустую. Я собираюсь инкапсулировать пару std :: vectors <> в внешний класс PointSet и использовать только то, что подходит на основе прецедента. Поскольку вы не можете перегружать константу возвращаемого типа, у меня будут только пары accessors: PointSet :: Foo() и PointSet :: ConstFoo(). Он выполнит свою работу. – dgnuff