Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > C/C++ программирование > Общие вопросы C/C++
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 25.08.2014, 12:12   #1
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию Объект способный хранить указатель на фукцию и на данные

Здравствуйте.
Из-за того, что меня не совсем устраивает std::function, решил написать собственную замену. Вышло примерно следующее:
Код:
#include <iostream>
#include <type_traits>


template <typename... _T>
class Function_ref;

template <typename _Ret, typename... _Args>
class Function_ref<_Ret(_Args...)>
{
    class Wrapper_base
    {
    public:
        virtual _Ret operator()(_Args... args) = 0;
    };

    template <typename _Fn>
    class Wrapper : public Wrapper_base
    {
        typename std::add_pointer<_Fn>::type fn;
    public:
        Wrapper(_Fn &fn) : fn(&fn) {}
        _Ret operator()(_Args... args)override
        {
            return (*fn)(args...);
        }
    };
    void *fn_storage;     //программа падает на обычной функции
    //int fn_storage[2];      //ok
    Wrapper_base *wrapper;
public:
    template <typename _Fn>
    Function_ref(_Fn &fn)
    {
        this->wrapper = new(&this->fn_storage) Wrapper<_Fn>{fn};
    }

    _Ret operator()(_Args... args) { return (*wrapper)(args...); }
};

 // Вспомогательная структура, для проверки
template <typename _T1, typename _T2>
struct Size_check
{
    static_assert(sizeof(typename std::add_pointer<_T1>::type) ==
                    sizeof(typename std::add_pointer<_T2>::type),
                  ""
                 );
    static void nop() {}
};

void ff(int) {std::cout << "hello from regular fn\n";}

int main()
{
    auto l = [](int){std::cout << "hello from lyambda\n";};
    Function_ref<void(int)> f = l;
    f(2);
    f = ff;
    f(2);

     // Проверка: указтель на лямбду и на обычную функциюю
     // имеют одинаковый размер.
    Size_check<decltype(ff), decltype(l)>::nop();
    return 0;
}
Внимание на fn_storage. Т.е. fn_storage должен быть в состоянии хранить указатели обоих типов (функции и данные/лямбда выражения). Т.к. Wrapper содержит всего один указатель, подумал что он вместится в void*.
Size_check демонстрирует равенство указателей двух типов. Но программа падает при работе с указателями на функцию. В онлайн компиляторах, программа падает и при использовании массива int'ов.

Вопросы:
1. Чем отличаются указатель на функцию от указателя на данные?
2. Каким должен быть объект, способный хранить оба типа указателя (не одновременно)?
220Volt вне форума Ответить с цитированием
Старый 25.08.2014, 12:32   #2
_Bers
Старожил
 
Регистрация: 16.12.2011
Сообщений: 2,329
По умолчанию

http://www.rsdn.ru/article/cpp/fastdelegate.xml
_Bers вне форума Ответить с цитированием
Старый 25.08.2014, 12:39   #3
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Цитата:
Сообщение от _Bers Посмотреть сообщение
жестко

В целом до меня дошло. Здесь: Wrapper(_Fn &fn) : fn(&fn) {}, когда передается обычная функция, берется указатель на указатель. Нужно написать прокладку для взятия адреса.
220Volt вне форума Ответить с цитированием
Старый 25.08.2014, 12:55   #4
_Bers
Старожил
 
Регистрация: 16.12.2011
Сообщений: 2,329
По умолчанию

Цитата:
Сообщение от 220Volt Посмотреть сообщение
жестко

В целом до меня дошло. Здесь: Wrapper(_Fn &fn) : fn(&fn) {}, когда передается обычная функция, берется указатель на указатель. Нужно написать прокладку для взятия адреса.
Судя по технике исполнения, ваш велосипед никогда не догонит std::function

std::function - это обертка над функторами, которые в свою очередь используют статическую связку между собой и вызываемой функцией.

Другими словами, эти функторы не хранят внутри себя указатель-на-функцию. А вместо этого фиксируют вызываемую функцию через параметр шаблона.

Связка через указатель на функцию - это динамическое связывание, которое не гарантирует 100% inline, использует передачу данных через аргументы функций, и отягощает sizeof функтора.

Пример фиксации вызываемой функции через параметр шаблона:
http://rextester.com/KYRE18162

Код:
#include <iostream>
using namespace std;

void foo(){ cout << "Free\n"; }


struct PrintHello{ static void foo(){ cout << "Hello\n"; }};
struct PrintWorld{ static void foo(){ cout << "World\n"; }};

template<class Func, Func func >struct FooBar
{
    void print()const{ func();} 
};

#define deduce( f ) decltype(&f), &f

int main()
{
    
    FooBar<      deduce(foo)        >   Free;         Free.print();
    FooBar< deduce(PrintHello::foo) >   Hello;        Hello.print();
    FooBar< deduce(PrintWorld::foo) >   World;        World.print();
 
    return 0;
}
_Bers вне форума Ответить с цитированием
Старый 25.08.2014, 13:04   #5
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Кто вам сказал, что std::function внутри себя ничего не хранит? Хранит, более того, выделяет помять под этот указатель через new, в куче. О производительности после этого ... . Моя реализация делает ссылки, размер которых известен, благодаря этому можно избавиться от выделения памяти в куче. Единственный ограничение - если function_ref инициализируется лямбдой, то лямбда должна существовать во время вызова. std реализация допускает такой вызов.
220Volt вне форума Ответить с цитированием
Старый 25.08.2014, 13:06   #6
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Кстати, проблемы не решил.
220Volt вне форума Ответить с цитированием
Старый 25.08.2014, 19:05   #7
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Я сам был виноват, не сразу осознал, что Wrapper_base имеет виртуальный метод, следовательно нужен доп. размер под таблицу вирт-ых функций. Получилось что-то такое:
Код:
#include <iostream>
#include <type_traits>


template <typename... _T>
class Function_ref;

template <typename _Ret, typename... _Args>
class Function_ref<_Ret(_Args...)>
{
    class Wrapper_base
    {
    public:
        virtual _Ret operator()(_Args... args) = 0;
    };

    template <typename _Fn>
    class Wrapper : public Wrapper_base
    {
        typename std::add_pointer<_Fn>::type fn;
    public:
        Wrapper(_Fn &fn) : fn(&fn) {}
        _Ret operator()(_Args... args)override
        {
            return (*fn)(args...);
        }
    };
    alignas(Wrapper<void()>)
    char wrapper_storage[sizeof(Wrapper<void()>)];
public:
    template <typename _Fn>
    Function_ref(_Fn &fn)
    {
        static_assert(std::extent<decltype(this->wrapper_storage)>::value ==
                        sizeof(Wrapper<_Fn>),
                      "out of range"
                     );
        static_assert(alignof(Wrapper<void()>) == alignof(Wrapper<_Fn>),
                      "mismatch of alignment"
                     );
        new(this->wrapper_storage) Wrapper<_Fn>{fn};
    }

    _Ret operator()(_Args... args)
    {
        return (*reinterpret_cast<Wrapper_base*>(this->wrapper_storage))(args...);
    }
};

int ff(int b, int a) {return a+b;}

int main()
{
    int ii = 2;
    auto l = [ii](int a, int b){return (a+b)*ii;};
    Function_ref<int(int,int)> f = l;
    std::cout << f(2, 4) << '\n';
    f = ff;
    std::cout << f(2, 4) << '\n';

    return 0;
}
Добавлять поддержку указателей на члены не стал, мне не надо.

Еще одна реализация (не моя) http://habrahabr.ru/post/159389/

Последний раз редактировалось 220Volt; 25.08.2014 в 22:14. Причина: Добавил alignas(Wrapper<void()>)
220Volt вне форума Ответить с цитированием
Старый 25.08.2014, 19:22   #8
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

В целом, я конечно велосипед написал. Здесь пишут,http://en.cppreference.com/w/cpp/uti...ction/function
что если оборачивать функции в std::reference_wrapper, то никаких выделений памяти из кучи не будет, всё внутри std::function.

Но в std библиотеке моего компилятора нет std::function. Так что пришлось этим заниматься.
220Volt вне форума Ответить с цитированием
Старый 26.08.2014, 21:41   #9
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Очередные грабли (компилятор ругается - нарушение strict aliasing, строчка с reinterpret_cast). http://habrahabr.ru/post/114117/.
Копаться во всех тонкостях нет времени. Можно ли положиться на такой код, с точки зрения попадание на грабли strict aliasing'a (на этот код предупреждений нет):
Код:
    template <typename _Ret, typename... _Args>
    class Function_ref<_Ret(_Args...)>
    {
        ...
        alignas(Wrapper<void()>)
        char wrapper_storage[sizeof(Wrapper<void()>)];
        Wrapper_base *wrapper;
    public:
        template <typename _Fn>
        Function_ref(_Fn &&fn)
        {
            this->wrapper = new(this->wrapper_storage) cur_wrapper_t{fn};
        }

        _Ret operator()(_Args... args)
        {
            return (*this->wrapper)(args...);
        }
    };
220Volt вне форума Ответить с цитированием
Старый 26.08.2014, 22:08   #10
_Bers
Старожил
 
Регистрация: 16.12.2011
Сообщений: 2,329
По умолчанию

Можно. Это хорошее решение.

Если:
Код:
 _Ret operator()(_Args... args)
    {
        return (*reinterpret_cast<Wrapper_base*>(this->wrapper_storage))(args...);
    }
заменить на:

Код:
 _Ret operator()(_Args... args)
    {
         Wrapper_base*  ptr = reinterpret_cast<Wrapper_base*>(this->wrapper_storage);
        return (*ptr)(args...);
    }
То проблем с strict aliasing так же не будет.

Тем не менее,
не рекомендую вам использовать чаровый буфер хранения без никто.

Поскольку в этом случае вас ожидает ситуация куда похуже, чем поломанный алиасинг: доступ к объекту по невыровненному адресу, что в общем случае является UB: на новых процессорах резко ухудшиться производительность. На старых процессорах может привести к поломке - крэш.

http://rextester.com/AFSG72652


Код:
//Title of this code

#include <iostream>
#include <cstddef>


struct storage
{
    char ar[32];
};

struct test
{
    char ch;
    storage st;
};


int main()
{
    int val = offsetof(test, st);
    int ost = val % sizeof(int);
    
    std::cout << "offset = "  << offsetof(test, st)<<'\n';
    std::cout << "ost = "  << ost<<'\n';
}
Как видите, чаровый буфер оказался расположен по невыровненому адресу.
Если реинтерпретировать его, как объект класса, то доступ к этому объекту так же получится по не выровненному адресу.

Самый простой способ разом решить и проблему алиасинга, и проблему неровных адресов - после чарового буфера добавить указатель.
И работать с объектом через этот указатель:
http://rextester.com/LVH79815
Код:
//Title of this code

#include <iostream>
#include <cstddef>

struct base;

struct storage
{
    char ar[32];
    base* ptr;
    
};

struct test
{
    char ch;
    storage st;
};


int main()
{
    int val = offsetof(test, st);
    int ost = val % sizeof(int);
    
    std::cout << "offset = "  << offsetof(test, st)<<'\n';
    std::cout << "ost = "  << ost<<'\n';
}
Так что ответ на ваш вопрос - да. Вы правильно делаете.
_Bers вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Как лучше хранить игровые данные CeHTuJI Общие вопросы Delphi 3 27.09.2013 15:15
Классы. Указатель на объект в классе. Son Помощь студентам 2 21.05.2013 18:45
Как хранить иерархические данные в реляционной бд? Хару Атари Помощь студентам 1 24.03.2013 17:46
Указатель на объект внутри класса WildTaburet Visual C++ 1 01.10.2012 14:34
где хранить данные принято? yuran111 Общие вопросы Delphi 36 12.05.2011 00:23