Сделать обратный вызов временным на VS 2010 - PullRequest
1 голос
/ 18 декабря 2010

У меня есть реализация обратного вызова, использующая ссылки rvalue для хранения аргументов, которая прекрасно работает с gcc, но не удается скомпилировать в VS 2010 для некоторого кода.Короткая версия:

#include <iostream>
#include <string>

class B {
public:
    virtual void execute() = 0;
};

template<typename FuncType, typename ArgType>
class F : public B {
public:
    F(FuncType func, ArgType && arg) : f(func), arg(arg) {}
    void execute() { f(arg); }
private:
    FuncType f;
    ArgType && arg;
};

template<typename FuncType, typename ArgType>
B * registerFunc(FuncType func, ArgType && arg)
{
    return new F<FuncType, ArgType>(func, arg);
}

void myFunction(std::string text)
{
    std::cout << "Function " << text << " here" << std::endl;
}

int main()
{
    const char text1[] = "sample1";
    std::string text2("sample2");

    B * b = registerFunc(myFunction, text1);
    b->execute();
    delete b;

    b = registerFunc(myFunction, text2);
    b->execute();
    delete b;

    // VS 2010 fails because of this call
    b = registerFunc(myFunction, text2.c_str());
    b->execute();
    delete b;

    return 0;
}

С gcc 4.4 это выдает:

$ g ++ clbck.cpp -std = c ++ 0x -o clbck && ./clbck
Функция sample1 здесь
Функция sample2 здесь
Функция sample2 здесь

Однако не удается выполнить компиляцию в VS 2010 при попытке создать экземпляр registerFunc из-за отмеченной строки:

ошибка C2664: «F :: F (FuncType, ArgType &&)»: невозможно преобразовать параметр 2 из «const char *» в «const char * && '
с помощью
[
FuncType = void (__cdecl *) (std :: string),
ArgType = const char *
]
Невозможно привязать lvalue к ссылке на rvalue

При поиске Google обнаружила аналогичную ошибку с Boost1.44 на VS2010, но рекомендуемое решение не использовать ссылки на значения вообще.Неужели нет другого пути?

И пока вы занимаетесь этим, что-то не так с тем, как я обрабатываю эти обратные вызовы?Он отлично работает с указателями функций и функторами (мне еще предстоит протестировать лямбды), единственным недостатком, который я обнаружил, является описанный выше.(Имейте в виду, что приведенный здесь код является лишь небольшой демонстрацией, в реальном коде я не даю пользователю никаких указателей; я фактически использую это для выполнения функций в различных потоках в приложении Qt).

Ответы [ 3 ]

2 голосов
/ 18 декабря 2010

Я считаю, что Visual Studio права жаловаться, и вы можете найти объяснение здесь .

В registerFunc выражение arg является lvalue ( имеет имя ).Если вы хотите переслать в качестве ссылки на значение, вы должны использовать std::forward:

template<typename FuncType, typename ArgType>
B * registerFunc(FuncType func, ArgType && arg)
{
    return new F<FuncType, ArgType>(func, std::forward<ArgType>(arg));
}

Та же проблема возникает в конструкторе FuncType, но при добавлении std::forwardтам выдает интересное предупреждение:

ссылочный элемент инициализируется как временный, который не сохраняется после выхода из конструктора

К сожалению, мне не хватает ссылок на rvalueчтобы помочь вам в дальнейшем, но я сомневаюсь, что ссылочный член rvalue имеет какой-либо смысл: ссылки на rvalue связываются с объектами, которые скоро умрут, имеет ли смысл хранить это?

1 голос
/ 18 декабря 2010

Не совсем ясно, что вы надеетесь достичь в отношении референсного члена rvalue.Это просто не выглядит правильно.Вы должны избегать хранения ссылки.Вы по-прежнему можете использовать такие вещи, как std :: ref и std :: cref, если вы хотите, чтобы объект функции сохранял объект, похожий на ссылку (очень похоже на поведение std :: bind).

Проблема, с которой вы столкнулись, заключается вчто нельзя инициализировать ссылку на rvalue значением lvalue целевого типа.Но вы пытаетесь сделать это, потому что параметр конструктора "arg" является с именем rvalue, что делает его lvalue выражением в списке инициализатора.Вы, вероятно, использовали старую версию GCC для компиляции этого кода.Более новые версии также будут жаловаться на это.

Вы можете избавить себя от некоторых проблем, полагаясь на std :: bind:

template<class FuncType>
class F : public B {
public:
  explicit F(FuncType func)
  : f(std::forward<FuncType>(func))
  {}
  void execute() { f(); }
private:
  FuncType f;
};

....

auto fun = std::bind(myFunction,text2.c_str());
B* ptr = new F<decltype(fun)>(fun);

Если вы действительно хотите разобраться с привязкой параметров самостоятельно, выдолжен сделать это так:

template<class FuncType, class ParamType>
class F : public B {
public:
  F(FuncType func, ParamType para)
  : f(std::forward<FuncType>(func))
  , p(std::forward<ParamType>(para))
  void execute() { f(p); }
private:
  FuncType f;
  ParamType p;
};

Имейте в виду, что тип параметра T &&, где T может быть выведен, имеет особое значение.Это универсальный параметр.Вывод аргумента шаблона сделает T ссылкой lvalue (и T && также по правилам свертывания ссылок) в случае, если аргумент был lvalue.Поэтому, если вы всегда хотите, чтобы параметр был сохранен как копия, вы должны написать

template<class FuncType, class ArgType>
B * registerFunc(FuncType func, ArgType && arg)
{
  typedef typename std::decay<ArgType>::type datype;
  return new F<FuncType,datype>(func, std::forward<ArgType>(arg));
}

Я бы предпочел функцию registerFunc, подобную этой.По умолчанию он копирует объект функции и объект параметра.Переопределить это можно через std :: ref и std :: cref:

registerFunc(some_func,std::cref(some_obj));
0 голосов
/ 19 декабря 2010

Почему бы просто не использовать лямбда-выражения?

class B {
    virtual void execute() = 0;
};
template<typename T> class F : public B {
    T t;
public:
    F(T&& arg) : t(std::forward<T>(arg)) {}
    void execute() { return t(); }
};
template<typename T> F<T> make_f(T&& t) {
    return F<T>(std::forward<T>(t));
}
int main() {
    std::string var;
    B* callback = make_f([=]() {
        std::cout << var << std::endl;
    });
}

Или, действительно, std::function<void()>, для чего он и нужен.

...