psilogic: (Default)
psilogic ([personal profile] psilogic) wrote2009-07-19 06:21 pm

Зодачка

Специально для любителей шОблоноф и прочих C++ наворотов. :) Сразу пердупердяю: задачка с подвохом.

Дано.

В программе есть некоторое количество классов, которые являются элементами односвязных списков с internal storage (проще говоря, это когда указатель на следующий элемент является членом класса).

Классы имеют вид:
class MyClass...
{
   MyClass *nextListItem;


Причем, варьируется не только название класса, но и название поля "nextListItem".

Задача.

Написать универсальную функцию вставки в начало списка с применением template, inline и pointer-to-member операторов. На входе должны быть: вставляемый элемент; указатель на первый элемент; название поля, указывающего на следующий элемент и тип класса. Функция может быть глобальной или членом namespace или функцией какого-либо нового класса - важно, чтобы она была одна, но работала для всех тех классов. Функция должна работать примерно так же, как ниже представленный #define:

#define INSERT_TO_LIST(firstItemPtr, item, nextItemName) \
(item)->nextItemName= (firstItemPtr), (firstItemPtr)= (item)


Посчитать количество строк в полученной функции. Факультативно: перечислить и обосновать преимущества перед приведенным #define.

мм?

[identity profile] snusmumrikkk.livejournal.com 2009-07-19 07:12 pm (UTC)(link)
template
inline T* unshift(T* &list, T* item, T* T::*member)
{
	item->*member = list;
	return list = item;
}


Что я пропустил?

Re: мм?

[identity profile] snusmumrikkk.livejournal.com 2009-07-19 07:14 pm (UTC)(link)
Сорри, параметр шаблона — <class T>

Re: мм?

[identity profile] psilogic.livejournal.com 2009-07-19 07:21 pm (UTC)(link)
Практически идеально! А подвох "в уме" заметить, конечно, сложно. Дело в том, что по условию поле-указатель на следующий является private. Соответственно при попытке написать вызов этой функции (иначе как изнутри класса) компилятор ругнется матом :)

Re: мм?

[identity profile] snusmumrikkk.livejournal.com 2009-07-19 07:26 pm (UTC)(link)
У твоего макроса та же проблема по идее ;).
По-твоему ее можно решить? (не говори пока решение, если ты его знаешь)

Re: мм?

[identity profile] psilogic.livejournal.com 2009-07-19 07:40 pm (UTC)(link)
Да, у макроса та же ерунда. Единственный плюс: макрос хотя бы можно вызвать из функции класса MyClass. А эту функцию даже так не вызовешь. Совсем идеального решения я не знаю, неидеальных, но работающих приходит в голову много. Мне интересно, что ты предложишь.

Re: мм?

[identity profile] snusmumrikkk.livejournal.com 2009-07-19 08:11 pm (UTC)(link)
Гм, изнутри класса прекрасно вызывается. А извне и не должна, мне кажется, хотя бы чтоб не дискредитировать слово private. Я бы сильно расстроился, если б это было возможно (без изменения тела класса).

Re: мм?

[identity profile] psilogic.livejournal.com 2009-07-19 08:36 pm (UTC)(link)
Изнутри другой функции класса прекрасно вызывается, если ты сделаешь саму функцию unshift методом класса. А если оставишь ее внешней, то получится, что ты обращаешься из функции класса к внешней функции unshift которая уже в свою очередь пытается обратиться к приватному полю member класса. И компилятор ругается (возможно, не любой). А #define разворачивается непосредственно в операторы, которые исполняются как часть функции класса, что разрешено.

Да, тело класса, конечно, придется курочить так или иначе. Лично для себя я сделал так:

template <class Type>
class ListHelper
{
public:
    static inline void insert(Type *val, Type *&first, Type * Type::*next) 
    {
        val->*next = first;
	first = val;
    }
};


В самом классе MyClass приходится использовать friend:

class MyClass...
{
    friend class ListHelper<MyClass>>;
    MyClass *nextItem;


Ну и вызов так:

ListHelper<MyClass>::insert(item, first, &MyClass::nextItem);


Позитив этого подхода -
1. Сакральное поле nextItem по-прежнему закрыто от некорректного использования.
2. В ListHelper можно написать еще кучу функций для работы со списком, и под каждую новую функцию уже не надо будет писать "friend" и модифицировать MyClass.

Re: мм?

[identity profile] snusmumrikkk.livejournal.com 2009-07-20 07:24 am (UTC)(link)
#include <iostream>

template <class T>
inline T* unshift(T* &list, T* item, T* T::*member)
{ item->*member = list; return list = item; }

template <class T>
struct helper {
    static inline void insert(T *val, T *&first, T *T::*next) 
    { val->*next = first; first = val; }
};

class item {
	item* next;
	friend class helper<item>;
public:
	item(): next(0) {}
	item* getNext() { return next; }
	void prependList(item* &list) { unshift(list, this, &item::next); }
};

int main()
{
	item *a = new item();
	item *b = new item();
	b->prependList(a); // OK

	a = new item();
	b = new item();
	helper<item>::insert(a, b, &item::next);
        // G++ 3.4.5. error: `item*item::next' is private
        // MSVC 9. error C2248: 'item::next' : cannot access private member declared in class 'item'
}

Что-то не то.

> А если оставишь ее внешней, то получится, что ты обращаешься из функции класса к внешней функции unshift которая уже в свою очередь пытается обратиться к приватному полю member класса.
Внешняя функция unshift пытается обратиться не к приватному полю, а к некому (по указателю). Права на доступ к полю, чтоб создать указатель на него, нужны только создателю указателя (в данном случае пользователю unshift, item::prependList()).

По этой же причине твой код не работает: для вызывающего кода такого понятия, как MyClass::nextItem, не существует. Контроль доступа ведь производится на уровне имен (это не Ява какая-нибудь).

Так что я не могу придумать решения, требующее только подружить класс item с кем-нибудь. Указатель на next по-любому приходится создавать внутри item.

Re: мм?

[identity profile] psilogic.livejournal.com 2009-07-20 07:47 am (UTC)(link)
извини, работать надо, позже отвечу. у меня оно просто в реальном проекте работает, но сходу не увидел, почему твой пример - не компилится.

Re: мм?

[identity profile] psilogic.livejournal.com 2009-07-20 09:02 am (UTC)(link)
friend class helper>item<;
- тут надо struct вместо class

В данном случае main должна быть объявлена friend класса item. Так что приватность все-таки срабатывает, но не там, где я думал. У меня оно работает по стечению обстоятельств - то поля public, то вызов из самого класса item. :) Надо будет придумать что-нибудь по-лучше на тот случай, когда перестанет работать.

Re: мм?

[identity profile] snusmumrikkk.livejournal.com 2009-07-20 09:29 am (UTC)(link)
Ну когда main дружит c item, и без helper'а все будет ок ;). И для вызова unshift из самого item не нужен helper.

Единственный разумный вариант, который потребует от item только дружбы — писать специализации функции а-ля:
template T*T::* getNext();
template<> item*item::* getNext() {
	return &item::next;
}

Но это уже онанизм очень похожий на вариант юзера dii.

Re: мм?

[identity profile] psilogic.livejournal.com 2009-07-20 09:37 am (UTC)(link)
Ага... но хочецца все-таки что-нибудь придумать. Хотя оно и так работает, но... :)