class A {
public:
void process() {}
};
class B {
public:
B(const A* a)
: m_a(a)
{}
const A* getA() const {return m_a;}
private:
const A* m_a;
};
void f(const std::vector<A*>& aa, const B& b) {
// Известно, что указатель хранящийся в b так же содержиться и в aa.
// Нужно сделать так:
b.getA()->process(); // Ошибка! b.getA() возвращает const A*, а нужно A*
}
Подскажите пожалуйста какой лучший способ снять константность с указателя на A в функции f?
Можно сделать const_cast — но это запрещено в code style.
Снимать константность с B::m_a не хочется, так как это повлечёт за собой снятие константности в куче других мест, как в приведённом фрагменте, так и за его пределами в реальной программе из которой этот фрагмент выделен. Тем более, что сам класс B никак не меняет экземпляр на который указывает m_a.
На текущий момент, я додумался только до того, чтобы искать в aa такое же значение, что и в b и использовать его.
Может быть в данной ситуации есть какое-то более простое, быстрое и красивое решение? Если вы его знаете, подскажите пожалуйста!
Может быть нужно слегка поменять структуру этого фрагмента? Мне нужно, чтобы экземпляр класса B мог ссылаться на конкретный экземпляр класса A и вызывать его константные функции. При этом экземпляр класса A реально храниться где-то в другом месте. Как именно организован этот механизм(через указатели или итераторы или что-то ещё) не принципиально.
Здравствуйте, Андрей Е, Вы писали:
АЕ>Подскажите пожалуйста какой лучший способ снять константность с указателя на A в функции f?
process изменяет состояние класса? Тогда у меня плохие новости — надо пересматривать дизайн, иначе не вижу проблемы сделать process константной, все остальное игры с UB.
Здравствуйте, Андрей Е, Вы писали:
АЕ>Может быть нужно слегка поменять структуру этого фрагмента?
задача мутная, но попробую предложить такое (метакод):
class A
{
public:
int GetID() const;
void process() {}
};
class B
{
public:
B(const A* a)
: m_a(a)
{}
int GetA_ID() const
{
return m_a->GetID();
}
private:
const A* m_a;
};
void f(const std::vector<A*>& aa, const B& b)
{
aa.findByID(b.GetA_ID())->process();
}
Здравствуйте, uzhas, Вы писали:
U>задача мутная, но попробую предложить такое (метакод):
В вашем варианте есть поиск по массиву, что может занять много времени. Хотелось бы, чтобы задача решалась за константное время.
И к тому же в данном случае, как мне кажется, id ничем не отличается от указателя. Указатель так же является уникальным для каждого объекта и занимает сравнительно немного места в памяти. Зато для него не нужно добавлять никаких функций в классы.
Здравствуйте, Андрей Е, Вы писали:
АЕ>В вашем варианте есть поиск по массиву, что может занять много времени. Хотелось бы, чтобы задача решалась за константное время.
тогда так: http://ideone.com/O7AWM
АЕ>Может быть нужно слегка поменять структуру этого фрагмента?
да. вам нужно убрать const там, где он мешается, чтобы реализовать быстрый алгоритм. либо надо пожертвовать слегка производительностью, добавив доп. уровень косвенности
Здравствуйте, Андрей Е, Вы писали:
АЕ>Может быть в данной ситуации есть какое-то более простое, быстрое и красивое решение? Если вы его знаете, подскажите пожалуйста!
Встречался с такими ситуациями. Мне кажется нормальным сделать const_cast, и написать комментарий рядом, что это для производительности. Типа неконстантый доступ есть, но долго искать объект. Можно еще отладочную проверку сделать, что указатель действительно в массиве есть.
Если const_cast жестко запрещен, то придется по-честному в массиве искать.
On 05/14/2012 03:17 PM, Sir-G wrote:
> Встречался с такими ситуациями. Мне кажется нормальным сделать const_cast, и > написать комментарий рядом, что это для производительности.
Но это же враньё, это не для производительности, а по разгильдяйству.
Зачем врать в комментах?
Здравствуйте, Андрей Е, Вы писали:
АЕ>Есть такой код: АЕ>
АЕ>void f(const std::vector<A*>& aa, const B& b) {
АЕ> // Известно, что указатель хранящийся в b так же содержиться и в aa.
АЕ> // Нужно сделать так:
АЕ> b.getA()->process(); // Ошибка! b.getA() возвращает const A*, а нужно A*
АЕ>}
АЕ>
Если известно априори, то зачем передавать вектор в функцию? (Правда, если это действительно априорная информация, то значит, что в программе слишком много const расставлено не по делу, либо наоборот, где-то их не хватает).
А если по этому вектору как-то пробегают — то заодно можно обнаружить в нём aa[i]==b.getA(), и вызвать aa[i]->process(). Или убедиться, что этого элемента там нет и выругаться.
C-style cast в данном случае фактически вызывает const_cast. Правда бывают такие странные code style, которые почему-то запрещают const_cast, но не запрещают C-style cast. В результате программисты пользуются этой лазейкой. Я считают, что при таком подходе, хотя и буква закона соблюдена, дух и смысл закона нарушен. const_cast происходит, хотя он замаскирован другой синтаксической конструкцией.
Здравствуйте, Sir-G, Вы писали:
SG>Встречался с такими ситуациями. Мне кажется нормальным сделать const_cast, и написать комментарий рядом, что это для производительности. Типа неконстантый доступ есть, но долго искать объект. Можно еще отладочную проверку сделать, что указатель действительно в массиве есть.
Видимо придётся так и сделать.
Здравствуйте, MasterZiv, Вы писали:
MZ>Но это же враньё, это не для производительности, а по разгильдяйству. MZ>Зачем врать в комментах?
Здравствуйте, Кодт, Вы писали:
К>Если известно априори, то зачем передавать вектор в функцию? (Правда, если это действительно априорная информация, то значит, что в программе слишком много const расставлено не по делу, либо наоборот, где-то их не хватает).
К>А если по этому вектору как-то пробегают — то заодно можно обнаружить в нём aa[i]==b.getA(), и вызвать aa[i]->process(). Или убедиться, что этого элемента там нет и выругаться.
Это фрагмент кода более сложной программы. На самом деле вектор нам доступен как член данных того же класса, которому принадлежит и фукнция f.
const не может стоять не по делу — программа не будет компилироваться. А вот нехватка вполне возможна.
В рамках этой функции по вектору никто не пробегает. Собственно в рамках этой функции и нужно вызвать process именно для того A, на который ссылается именно этот B. При этом мы находимся вне B и нам доступен неконстантный A, так что константность B вроде как мешать не должна.
Здравствуйте, Андрей Е, Вы писали:
К>>Если известно априори, то зачем передавать вектор в функцию? (Правда, если это действительно априорная информация, то значит, что в программе слишком много const расставлено не по делу, либо наоборот, где-то их не хватает).
К>>А если по этому вектору как-то пробегают — то заодно можно обнаружить в нём aa==b.getA(), и вызвать aa[i]->process(). Или убедиться, что этого элемента там нет и выругаться.
АЕ>Это фрагмент кода более сложной программы. На самом деле вектор нам доступен как член данных того же класса, которому принадлежит и фукнция f.
АЕ>const не может стоять не по делу — программа не будет компилироваться. А вот нехватка вполне возможна.
Ещё как может стоять не по делу. Убери все const из программы — и она снова станет компилироваться так что это не аргумент.
Если ты знаешь, что объект A, переданный в B, нужно [i]будет менять по ссылке из B, — зачем там константность?
Если в точке конструирования B объект A константный (например, эта точка — константный метод A), — то надо понимать: отдача константного указателя на сторону и в будущее — это обещание, что объект A ни сейчас не меняется, ни в будущем (что, очевидно, не так). Так, может быть, следует снять константность с объекта A прямо там и тогда? Возможно, что отследить всю цепочку, где константность обещана, но не оправдана.
Либо это не указатель, а хэндл, служащий лишь для идентификации объекта.
А с хэндлами разговор или длинный (лезть в таблицу — в тот самый вектор — и искать), или короткий (да хоть reinterpret_cast), если ты знаешь его внутреннюю природу.
АЕ>В рамках этой функции по вектору никто не пробегает. Собственно в рамках этой функции и нужно вызвать process именно для того A, на который ссылается именно этот B. При этом мы находимся вне B и нам доступен неконстантный A, так что константность B вроде как мешать не должна.
Я вот чувствую, что где-то в общем дизайне программы есть недочёт. По крайней мере, пара версий:
— больше, чем 2 (константность/неконстантность) уровня прав использования
— больше, чем 2 (public/private) области доступа
Но это сложно обсуждать, не вдаваясь в подробности. Поэтому сейчас забьём.
On 05/15/2012 07:21 AM, Андрей Е wrote:
> MZ>Но это же враньё, это не для производительности, а по разгильдяйству. > MZ>Зачем врать в комментах? > > А в чем состоит разгильдяйство?
В том, что вместо того, чтобы правильно спроектировать код, ты пишешь горбуху.
On 05/15/2012 01:19 PM, Кодт wrote:
> Быстрое же решение — вот такое: добавить ассерт. > > assert( std::find(aa.begin(), aa.end(), b.getA()) != aa.end() ); > const_cast<A*>(b.getA())->process();
Тут вопрос в другом, -- нафига вообще вызывать эту функцию
process(), которая неконстантная, через класс, содержащий константную ссылку ?
Надо эту проблему решать, а не людей в форуме вопросами мучать.
Здравствуйте, Андрей Е, Вы писали:
АЕ>Подскажите пожалуйста какой лучший способ снять константность с указателя на A в функции f? АЕ>Можно сделать const_cast — но это запрещено в code style.
Бредовый code style, значит — программеры будут изворачиваться через сишный каст со скобками, который увидеть довольно трудно, в отличие от const_cast, который виден в коде, доступен для банального текстового поиска и четко выражает намерения программиста.
АЕ>На текущий момент, я додумался только до того, чтобы искать в aa такое же значение, что и в b и использовать его.
вот пример бредового решения, вызванного исключительно идиотским code style.
АЕ>Может быть в данной ситуации есть какое-то более простое, быстрое и красивое решение? Если вы его знаете, подскажите пожалуйста!
const_cast или, если человек на код-ревью непробиваем, сишный каст.
АЕ>Тем более, что сам класс B никак не меняет экземпляр на который указывает m_a.
У тебя класс В спроектирован так, что может указывать на абсолютно любой объект типа А (и истинно константный в том числе), и гарантирует, что он его менять не будет.
Таким образом код, который работает с произвольным объектом типа В, не имеет права снимать константность.
В твоем же случае у тебя в распоряжении есть дополнительная информация, что объект А, на который указывает пришедший объект В — точно неконстантный.
Эта информация у тебя в коде никак не выражена (ее можно было бы выразить, например, специальным классом В_non_const), так что остается только const_cast и соответствующий комментарий. Плюс в твоем случае есть возможность проверить, что В указывает на объект именно из вектора — это можно сунуть в ассерт в отладочной версии.
В_non_const можно отнаследовать от В и просто добавить GetNonConstA с конст-кастом внутри. В таком случае все, кто ожидает на входе В, его и получат, а те места, которые знают, что там на самом деле В_non_const, будут иметь доступ к неконстантному А. Но возможность такого решения очень сильно зависит от текущих взаимоотношений классов и от того, чем на самом деле занимается В в твоей программе, а не в этом примере, и все может запросто оказаться невозможным.
Еще один вариант выразить явно знание о том, у тебя все объекты лежат в векторе, можно, создав специальный вариант В, который будет держать итераторы внутрь вектора, а не указатели. Или индексы, если вектор меняется (но тогда и нынешнее решение с прямыми указателями не работало бы). Но, опять же, это зависит от того, куда у тебя еще уходит В.
Но если В — это внутренний класс, то убери константность его члена и не парься.
Здравствуйте, MasterZiv, Вы писали:
MZ>Тут вопрос в другом, -- нафига вообще вызывать эту функцию MZ>process(), которая неконстантная, через класс, содержащий константную ссылку ? MZ>Надо эту проблему решать, а не людей в форуме вопросами мучать.
У этой проблемы может оказаться единственное нереалистичное решение "переписать все нафиг"
Ну как вариант pImpl использовать. Храним в B указатель на неконстантный объект A, и без проблем возвращаем его в getA(). Всю реализацию B выносим в BImpl, и там уже используем указатель на константный объект А.
>> У этой проблемы может оказаться единственное нереалистичное решение "переписать >> все нафиг"
MZ>Во-первых, 50/50 -- может оказаться, а может и не оказаться, т.е. есть и ещё MZ>какое-то решение. А 50% -- это уже хороший шанс.
50% соответствуют твоему опыту? А то у меня вот опыт гораздо более печальный в этом смысле.
Здравствуйте, MasterZiv, Вы писали:
MZ>50/50 -- может оказаться, а может и не оказаться, т.е. есть и ещё MZ>какое-то решение. А 50% -- это уже хороший шанс.
вообще-то, если исхода два, то это не означает, что они равновероятны
> MZ>50/50 -- может оказаться, а может и не оказаться, т.е. есть и ещё > MZ>какое-то решение. А 50% -- это уже хороший шанс. > > вообще-то, если исхода два, то это не означает, что они равновероятны
Правда ? А я всегда считал, что равновероятны, и попадал в лучший для
меня. Вот я везунчик!
Здравствуйте, MasterZiv, Вы писали:
MZ>Тут вопрос в другом, -- нафига вообще вызывать эту функцию MZ>process(), которая неконстантная, через класс, содержащий константную ссылку ? MZ>Надо эту проблему решать, а не людей в форуме вопросами мучать.
Ну, у меня есть одно подозрение.
Пусть у нас есть некая модель (например, дерево XML DOM) и некий контроллер, владеющий этой моделью.
Наружу модель отдаётся как константная: нечего кому попало в ней ковыряться, это задача контроллера.
А дальше мы снаружи сообщаем контроллеру, что хотим что-то поковырять с его позволения и его же руками.
std::cout << controller.theModel()->fooItem()->barItem()->buzItem()->name(); // можно
controller.theModel()->fooItem()->barItem()->buzItem()->process(); // нельзя
controller.pleaseModify( controller.theModel()->fooItem()->barItem()->buzItem() ); // можно
anotherController.pleaseModify( controller.theModel()->fooItem()->barItem()->buzItem() ); // нужно дать по пальцам
Об этом я и говорил — про права доступа. Здесь нужно было провести границу "свой-чужой" с помощью private и friend, а провели границу "можно-нельзя" с помощью const.
То есть, простой выход — запихать A::process (и, видимо, многое другое) в private, и убрать ставшую ненужной константность.
Спасибо всем кто ответил. Я почитал ответы и понял, что задача сформулирована не совсем полно. Более полная формулировка такая:
Есть код:
class A {
public:
void process() {}
};
class B {
public:
B(const A* a)
: m_a(a)
{}
const A* getA() const {return m_a;}
private:
const A* m_a;
};
class C {
public:
void process() {
m_b.getA()->process(); // Ошибка! m_b.getA() возвращает const A*, а нужно A*
}
private:
std::vector<A> m_aa;
B m_b;
};
Есть ТРИ класса. Класс C владеет списком экземпляров класса A и экземпляром класса B, причём экземпляр класса B ссылается на один из экземпляров класса A из списка.
Так как класс C является владельцем, то он имеет право как угодно изменять свои экземпляры классов A и B.
Класс B должен иметь возможность доступаться до константых методов своего объекта класса A, так как ему нужна информация о его состоянии.
Класс C в функции process хочет вызвать неконстантную функцию process именно того объекта класса A на который ссылается его объект класса B.
Возможно в этом случае решение найти проще.
Глядя на эту схему возникает мысль, что возможно проблема состоит в том, что мы пытаемся использовать указатель B::m_a для двух разных целей: для получения информации об A изнутри B и для связи объекта B и A в классе C.
Хотя с другой стороны если хранить ещё один указатель или индекс или итератор, то вроде как возникает дублирование одной и той же информации.
АЕ>class A {
АЕ>private:
АЕ> void process() {}
friend class C;
АЕ>};
АЕ>
И сделать A* неконстантным в классе B — больше там константность не нужна.
А если политик доступа много (т.е. реально у тебя не три класса), то, возможно, придётся или как-то переразбивать классы, или изгаляться, или просто забить в желании переложить ответственность за прямоту своих рук на компилятор. Быть внимательным, короче говоря
Пример переразбивания
class AforC
{
protected: // потому что для класса A это тоже можноvoid process(); // только для класса C, и больше ни для когоfriend class C;
};
class AforD
{
protected:
void perform();
friend class D;
};
class A: public AforC, public AforD {};
Если компоненты должны знать друг о друге и о композитном классе — то либо forward declaration и вынести все определения за объявление класса A,
class AforC
{
protected:
friend class A;
friend class AforD;
friend class C;
void process();
};
class AforD
{
protected:
friend class A;
friend class D;
void perform();
};
class A: public AforC, public AforD
{
public:
void execute() { process(); perform(); } // можно инлайнить, ибо объявления выше
};
// а эти определения вынесены из объявления классовinline void AforC::process() { static_cast<A*>(this)->perform(); }
inline void AforD::perform() { static_cast<A*>(this)->execute(); }
Либо — то же самое, но с помощью CRTP
template<class A> class AforC
{
protected:
friend class AforD;
friend class C;
void process() { static_cast<A*>(this)->perform(); }
};
template<class A> class AforD
{
protected:
friend class A;
friend class D;
void perform() { static_cast<A*>(this)->execute(); }
};
class A: public AforC<A>, public AforD<A>
{
public:
void execute() { process(); perform(); } // можно инлайнить, ибо объявления выше
};