STL std :: map, передать ref по const и необходимость const_casting - PullRequest
3 голосов
/ 30 ноября 2010

У меня есть простой вопрос относительно const_cast и наилучших практик, касающихся контейнеров STL. Рассмотрим следующее, когда класс Foo имеет собственный STL std::map от Widget* до int:

Декларация:

#include <map>  
using std::map;

class Widget;

class Foo {
public:
     Foo(int n);
     virtual ~Foo();

     bool hasWidget(const Widget&);

private:
     map<Widget*,int> widget_map;
};

Определение:

#include <map>
#include "Foo.h"
#include "Widget.h"

using std::map;

Foo::Foo(int n)
{
     for (int i = 0; i < n; i++) {
          widget_map[new Widget()] = 1;
     }
}

Foo::~Foo()
{
     map<Widget*, int>::iterator it;
     for (it = widget_map.begin(); it != widget_map.end(); it++) {
          delete it->first;
     }
}

bool Foo::hasWidget(const Widget& w)
{
     map<Widget*, int>::iterator it;
     it = this->widget_map.find(const_cast<Widget*>(&w));
     return ( ! ( it == widget_map.end() ) );
}

Учитывая, что hasWidget принимает в качестве параметра ссылку на const, константность необходимо отбрасывать при вызове map::find (wiget_map от Wiget* до int). Насколько я могу судить, такой подход является разумным и желательным, но я не хочу принимать его как таковой без обратной связи от более опытных программистов на C ++.

Мне кажется, что это один из немногих случаев правильного использования const_cast, учитывая, что мы передаем результат приведения в метод STL. Я прав?

Я понимаю, что другие перестановки этого вопроса уже были заданы (например, const_cast для вектора с объектом ), но, похоже, ни одна из них не имеет прямого отношения к вышеупомянутому.

Заранее спасибо.

Ответы [ 6 ]

1 голос
/ 30 ноября 2010

Это выглядит неуклюже для меня.Идентификация объектов по их физическим адресам довольно «особенная», по общему признанию, она уникальна, но это также странно.

Я бы настоятельно рекомендовал изменить карту:

std::map<Widget::Id, Widget*>

, где Widget::Id может простобыть int или аналогичным.

Тогда не было бы никаких проблем с константностью.

Чтобы углубиться, вы также можете взглянуть на Boost PointerКонтейнер Библиотека:

boost::ptr_map<Widget::Id, Widget>

, которая устранит проблемы с управлением памятью.

1 голос
/ 30 ноября 2010

Карта с ключом, указателем которого является указатель, громоздка - единственный способ найти ее - иметь такой же указатель . Чтобы это работало, вы должны гарантировать, что метод hasWidget будет вызван с объектом с тем же адресом!

Конечно, вы должны правильно реализовать Widget, чтобы в нем были перегружены правильные операторы, которые будут действовать как ключ в std::map! На вашей карте вы можете просто иметь:

std::map<Widget, int>

И тогда ваша находка не нуждается в const_cast!

1 голос
/ 30 ноября 2010

Почему бы не заменить hasWidget на Widget*? В настоящее время интерфейс выглядит сложным, поскольку он подразумевает, что вы ищете виджеты по значению на базовой карте, когда вы на самом деле ищете их по адресу. Метод также должен быть const, я считаю:

bool hasWidget(Widget *) const;
1 голос
/ 30 ноября 2010

Да, это разумное использование const_cast<>. Вы должны рассмотреть возможность создания hasWidget const.

1 голос
/ 30 ноября 2010

Почему бы не использовать map<const Widget*,int>? Похоже, вы никогда не изменяли виджет, на который указывает ни один из ключей на вашей карте.

Если есть веская причина, тогда да, я думаю, что вы правы. При вызове кода, который гарантированно не изменяет ссылку на указатель, безопасно отбрасывать const. Из-за того, что контейнеры указателей являются шаблонными, ни одна из их функций никогда напрямую не изменяет этот referand, но если содержащийся тип был константным указателем, то пользователи также не смогли бы изменить referand (без преобразования const). Конечно, безопаснее отбрасывать const перед поиском, чем отбрасывать const перед изменением, если он должен быть одним из двух ...

Кстати, hasWidget будет короче, если вы используете count вместо find. В целом также немного безопаснее (не в данном случае) использовать count, поскольку find с этим const_cast возвращает итератор, который можно использовать для изменения виджета, тогда как count - нет. Так что вам не нужно беспокоиться о том, что происходит с возвращаемым значением count. Очевидно, что возвращаемое значение в любом случае полностью под контролем.

1 голос
/ 30 ноября 2010

Я думаю, что я упаду в «субъективное и аргументированный» через мой ответ, но я дам ему выстрелили ...

Я не в ужасе от const_cast, но я скептически отношусь к вашему дизайну. Функция-член hasWidget получает свой параметр const ref: что это говорит клиенту? С точки зрения клиента, , если бы я не знал реализацию , я бы, вероятно, подумал, что каждый Widget сравнивается по значению с параметром. Для меня интерфейс не отражает фактическое поведение, которое сравнивает Widget по адресу .

Например, текущая подпись позволяет передавать временный Widget, хотя возвращаемое значение в этом случае никогда не может быть true. Я бы лично изменил бы подпись на (обратите внимание, что я добавил const):

bool hasWidget(const Widget *) const;
...