2010-03-27 15 views
2

J'écris une implémentation de rappel en C++.Problème de référence invalide C++

J'ai une classe de rappel abstraite, disons:

/** Abstract callback class. */ 
class callback { 
public: 

    /** Executes the callback. */ 
    void call() { do_call(); }; 
protected: 

    /** Callback call implementation specific to derived callback. */ 
    virtual void do_call() = 0; 
}; 

Chaque rappel que je crée (accepter des fonctions monoargumental, fonctions double arguments ...) est créé comme un mixin en utilisant une des options suivantes :

/** Makes the callback a single-argument callback. */ 
template <typename T> 
class singleArgumentCallback { 
protected: 
    /** Callback argument. */ 
    T arg; 

public: 
    /** Constructor. */ 
    singleArgumentCallback(T arg): arg(arg) { } 
}; 

/** Makes the callback a double-argument callback. */ 
template <typename T, typename V> 
class doubleArgumentCallback { 
protected: 
    /** Callback argument 1. */ 
    T arg1; 

    /** Callback argument 2. */ 
    V arg2; 

public: 
    /** Constructor. */ 
    doubleArgumentCallback(T arg1, V arg2): arg1(arg1), arg2(arg2) { } 
}; 

Par exemple, un rappel de la fonction unique arg ressemblerait à ceci:

/** Single-arg callbacks. */ 
template <typename T> 
class singleArgFunctionCallback: 
    public callback, 
    protected singleArgumentCallback<T> { 

    /** Callback. */ 
    void (*callbackMethod)(T arg); 

public: 
    /** Constructor. */ 
    singleArgFunctionCallback(void (*callback)(T), T argument): 
     singleArgumentCallback<T>(argument), 
     callbackMethod(callback) { } 

protected: 
    void do_call() { 
     this->callbackMethod(this->arg); 
    } 
}; 

Pour un confort d'utilisation, je voudrais avoir une méthode qui crée un rappel sans avoir l'utilisateur pense sur les détails, de sorte que l'on peut appeler (cette interface est pas sujette au changement, malheureusement):

void test3(float x) { std::cout << x << std::endl; } 
void test5(const std::string& s) { std::cout << s << std::endl; } 

make_callback(&test3, 12.0f)->call(); 
make_callback(&test5, "oh hai!")->call(); 

Mon la mise en œuvre actuelle de make_callback(...) est la suivante:

/** Creates a callback object. */ 
template <typename T, typename U> callback* make_callback(
    void (*callbackMethod)(T), U argument) { 
    return new singleArgFunctionCallback<T>(callbackMethod, argument); 
} 

Malheureusement, quand je l'appelle make_callback(&test5, "oh hai!")->call(); je reçois une chaîne vide sur la sortie standard. Je crois que le problème est que la référence sort de la portée après l'initialisation du rappel.

J'ai essayé d'utiliser des pointeurs et des références, mais il est impossible d'avoir un pointeur/référence à référence, donc j'ai échoué. La seule solution que j'avais était d'interdire le remplacement du type de référence par T (par exemple, T ne peut pas être std :: string &) mais c'est une solution triste car je dois créer une autre classe singleArgCallbackAcceptingReference acceptant un pointeur de fonction avec la signature suivante:

void (*callbackMethod)(T& arg); 

Ainsi, mon code est dupliqué 2 fois, où n est le nombre d'arguments d'une fonction de rappel.

Est-ce que quelqu'un sait une solution de contournement ou a une idée de comment résoudre ce problème? Merci d'avance!

+1

"J'essayé d'utiliser des pointeurs et des références, mais il est impossible d'avoir un pointeur/référence à la référence": 'int a = 1; int & a_ref = a; int * p_a_ref = &a_ref; (* p_a_ref) = 2; ' – mlvljr

+0

@mlvljr: Est-ce supposé être un pointeur vers une référence? Ce n'est pas. Le pointeur pointe sur 'a'. Vous ne pouvez simplement pas pointer vers une référence. – GManNickG

+0

@GMan "Est-ce supposé [d] être un pointeur vers une référence?" - bien sûr que non, cela montre juste comment obtenir [facilement] un pointeur vers quelque chose, bien, référencé par une référence (une chose que OP était probablement prêt à avoir mais ne savait pas), c'est-à-dire juste une autre) niveau d'indirection, rien d'autre. – mlvljr

Répondre

3

Le problème est que dans make_callback(), T devient const std::string&, qui devient à son tour T dans votre singleArgumentCallback. U, cependant, est un const char*, de sorte qu'un objet temporaire std::string est créé et lié à cette référence dans singleArgumentCallback. Lorsque make_callback() se termine, ce temporaire est détruit, laissant l'objet singleArgumentCallback créé avec une référence à un objet qui n'existe plus.

Ce que vous aurez à faire est de supprimer d'abord les références (et, peut-être, les qualificatifs cv) des types passés en make_callback().Comme Marcelo suggested, Boost.TypeTraits peut vous aider à le faire, mais si vous voulez, il est difficile de ne pas cuisiner quelque chose sur votre propre:

template< typename T > struct remove_ref  { typedef T result_type; }; 
template< typename T > struct remove_ref<T&> { typedef T result_type; }; 

changer ensuite make_callback() à:

template <typename T, typename U> 
callback* make_callback(void (*callbackMethod)(T), U argument) 
{ 
    typedef typename remove_ref<T>::result_type arg_type; 
    return new singleArgFunctionCallback<arg_type>(callbackMethod, argument); 
} 
+0

Super, merci pour cet indice! Ça n'a pas fonctionné, mais après quelques ... disons des 'correctifs', j'ai compris. La solution complète est affichée ci-dessous. Malheureusement, je ne suis pas autorisé à utiliser Boost. Mais merci, je m'en souviendrai à l'avenir! – Karol

0

Boost.TypeTraits pourrait aider. add_reference convertit les types concrets en types de référence tout en laissant les types de référence tels quels.

0

Merci à sbi, je suis arrivé ce genre de choses :-) travail

La solution que je fini avec est ici:

template <typename T> struct removeRef  { typedef T resultType; }; 
template <typename T> struct removeRef<T&> { typedef T resultType; }; 

/** Single-arg callbacks. */ 
template <typename T, typename U> 
class singleArgFunctionCallback: 
    public callback, 
    protected singleArgumentCallback<U> { 

    /** Callback. */ 
    void (*callbackMethod)(T arg); 

public: 
    /** Constructor. */ 
    singleArgFunctionCallback(void (*callback)(T), U argument): 
     singleArgumentCallback<U>(argument), 
     callbackMethod(callback) { } 

protected: 
    void do_call() { 
     this->callbackMethod(this->arg); 
    } 
}; 

template <typename T, typename U> 
callback* make_callback(void (*callbackMethod)(T), U argument) { 
    typedef T ArgumentType; 
    typedef typename removeRef<T>::resultType StrippedArgumentType; 
    return new singleArgFunctionCallback<ArgumentType, StrippedArgumentType>(callbackMethod, argument); 
} 

Si quelqu'un voit des améliorations possibles, je serais heureux d'apprendre!

Merci à tous, Karol