2010-11-24 29 views
4

Je travaille sur un petit outil de mémoire qui suit les allocations et les désallocations, les tailles d'objets, les types d'objets, etc. La méthode que je vais utiliser pour le suivi des fichiers sources, les numéros de ligne et les types d'objets fonctionne comme ceci:Problème d'interception et de collecte d'objets/d'objets lorsque l'utilisateur appelle l'opérateur nouveau

#define DEBUG_NEW SourcePacket(__FILE__, __LINE__) * new 
#define new DEBUG_NEW 

SourcePacket est juste une petite classe qui prend un const char * et un int pendant la construction. Ces valeurs sont renseignées via les macros __FILE__ et __LINE__. Le type d'objet est acquis comme ceci:

template<typename T> 
T* operator*(const SourcePacket& packet, T* p); 

p est le pointeur sur l'objet nouvellement affecté, dont le type est découvert en utilisant RTTI. Dans la surcharge de l'opérateur, les informations sont prises et stockées dans le traceur et le pointeur est transmis au programme. D'autres informations comme la taille et l'adresse sont saisies dans l'opérateur surchargé.

Maintenant, cette configuration a très bien fonctionné pour moi. Il ne fonctionne pas pour le code que je ne compile pas, naturellement, mais l'une des meilleures choses est qu'il joue bien avec le placement de nouveaux appels effectués par l'utilisateur, qui ne fonctionne pas à l'aide de la cité souvent

#define new new(__FILE__, __LINE__) 

méthode. Le problème que j'ai rencontré est que si l'utilisateur appelle l'opérateur new, le programme ne peut tout simplement pas compiler. Bien sûr, cela est parce que la macro se développe comme si

return operator SourcePacket("blahblah.cpp", 20) * new(size); 

au lieu de

return SourcePacket("blahblah.cpp", 20) * new(size); 

Je ne peux pas vraiment voir une façon de contourner cela. Je pourrais, bien sûr, juste enlever la nouvelle procédure SourcePacket * et juste laisser mon nouvel opérateur surchargé recueillir la taille et l'adresse, mais ce genre de défaites une grande partie de l'objectif de l'outil.

(De même, je ne cherche pas à créer Valgrind ou quoi que ce soit d'autre, et je sais que surcharger les opérations globales peut être plutôt douteux, surtout à des fins éducatives. Des fonctionnalités spécifiques peuvent être utilisées pour découvrir certaines de ces informations, mais je voudrais seulement utiliser le C++ standard pour qu'il soit multi-plateforme et indépendant des bits (x86, x64, etc.). Malheureusement, il ne semble pas y avoir de moyen d'utiliser l'un ou l'autre de façon conditionnelle, selon qu'il s'agit simplement d'un nouveau (ou d'un nouvel emplacement) ou d'un nouvel emplacement. opérateur nouveau. Ce n'est pas critique que je fais fonctionner cela, mais je serais intéressé d'entendre si quelqu'un a trouvé un moyen de contourner cette limitation.

Répondre

1

Nous avons besoin d'une expression qui est valide lorsque préfixé avec "opérateur" et quand ce n'est pas le cas. Cela signifie que nous devons définir un opérateur qui prend un SourcePacket. Il pourrait prendre d'autres arguments, mais il s'avère que ce n'est pas nécessaire. Unaire operator * fera bien:

const SourcePacket& operator *(const SourcePacket& sp) { 
    return sp; 
} 

#define DEBUG_NEW *(SourcePacket(__FILE__, __LINE__)) * new 
#define new DEBUG_NEW 

Puisque nous ne pouvons pas parenthésée pleinement la déclaration, il y a encore la possibilité d'erreurs si elles sont utilisées sauf dans les expressions les plus simples.

struct Chain { 
    Chain() : next(0) {} 
    Chain(Chain *n) : next(n) {} 
    ~Chain() {delete next;} 
    Chain* next; 
    Chain& operator *(Chain* b); 
}; 
Chain& Chain::operator *(Chain* b) { 
    if (b != next) { 
    if (next) { delete next; } 
    next = b; 
    } 
    return *this; 
} 

int main() { 
    Chain fetters; 
    /* since * is left associative, it tries to 
    call operator*(Chain&, const SourcePacket&) 
    */ 
    fetters * new Chain(); 
    // This compiles 
    fetters * (new Chain()); 
} 

Pour résoudre ce problème, nous devons définir l'opérateur approprié.Pour le type de retour, vous pouvez définir une hiérarchie de classes de modèles qui associe un argument left avec un SourcePacket et sont commutatives dans l'argument de droite ((a:A ⊙ b:SourcePacket) * c:C) = (a:A ⊙ c:C) * b:SourcePacket, où ⊙ est un opérateur C++ binaire). Quelque chose comme le suivant, mais sans les bogues qu'il possède sans aucun doute.

template <typename L, typename Rslt=L, typename R=const SourcePacket> 
struct PairedTraits { 
    typedef L Left; 
    typedef R Right; 
    typedef Rslt Result; 
    typedef PairedTraits<Result> ResultTraits; 
}; 

template <typename L, typename Traits = PairedTraits<L> > 
struct Paired { 
    typedef typename Traits::Left Left; 
    typedef typename Traits::Right Right; 
    typedef typename Traits::Result Result; 
    typedef Paired<typename Traits::Result, typename Traits::ResultTraits> ResultPaired; 

    Left& left; 
    Right& right; 

    Paired(Left& l, Right& r) : left(l), right(r) {} 
    operator Left&() {return left;} 

    template <typename A> 
    ResultPaired operator*(const C& c) const 
    {return ResultPaired(this->left * c, this->right); } 
}; 

template <typename L, typename Traits = PairedTraits<L> > 
struct MultPaired : Paired<L, Traits> { 
    typedef Paired<L, Traits> Base; 
    typedef Paired<typename Traits::Result, typename Traits::ResultTraits> ResultPaired; 

    MultPaired(typename Traits::Left& l, typename Traits::Right& r) : Base(l, r) {} 

    template <typename A> 
    ResultPaired operator*(const C& c) const 
    {return ResultPaired(this->left * c, this->right); } 
}; 

template <typename L, typename Traits = PairedTraits<L> > 
struct ModPaired : Paired<L, Traits> { 
    typedef Paired<L, Traits> Base; 
    typedef Paired<typename Traits::Result, typename Traits::ResultTraits> ResultPaired; 

    ModPaired(typename Traits::Left& l, typename Traits::Right& r) : Base(l, r) {} 

    template <typename A> 
    ResultPaired operator*(const C& c) 
    {return ResultPaired(this->left % c, this->right); } 
}; 

template <typename L, typename Traits = PairedTraits<L> > 
struct DivPaired : Paired<L, Traits> { 
    typedef Paired<Traits> Base; 
    typedef Paired<typename Traits::Result, typename Traits::ResultTraits> ResultPaired; 

    DivPaired(typename Traits::Left& l, typename Traits::Right& r) : Base(l, r) {} 

    template <typename A> 
    ResultPaired operator*(const C& c) const 
    {return ResultPaired(this->left/c, this->right); } 
}; 

Les enfants de apparié pourraient retourner un résultat (left ⊙ c ou left ⊙ (right * c), ce qui rend essentiellement associative droit *(const SourcePacket&, T)) plutôt qu'un appairés. Par exemple:

template <typename L, typename Traits = PairedTraits<L> > 
struct DivPaired : Paired<L, Traits> { 
    typedef Paired<L, Traits> Base; 

    MultPaired(typename Traits::Left& l, typename Traits::Right& r) : Base(l, r) {} 

    template <typename A> 
    Result operator*(const C& c) const 
    {return this->left/(this->right * c); } 
}; 
+0

Merci pour cette réponse détaillée outis! Je pense que j'obtiens ce que tu vis, mais je dois admettre que je ne suis pas sûr d'avoir compris l'essentiel. Avant votre modification, j'ai trouvé que le changement de macro faisait que le compilateur se plaignait d'une erreur de syntaxe en point-virgule, mais le problème semble plus grand que cela. Une chose qui me harcèle en passant par ce problème est qu'en insérant l'opérateur unaire dans le mix et en ajoutant la configuration commutative pour gérer correctement l'expression, l'intention de l'énoncé pourrait changer - cela pourrait finir par changer un opérateur. "appeler dans une" nouvelle "opération normale. – Gemini14

+0

Notez que si les appels à 'new' apparaissent seulement du côté droit des affectations et des expressions entre parenthèses, vous n'aurez pas besoin de traiter l'associativité gauche de' * ', ce qui simplifie grandement les choses. En ce qui concerne la perte de 'operator', vous pourrez peut-être bricoler quelque chose basé sur deux' new' surchargés plutôt que sur le unary '*' (vous pourriez aussi avoir besoin de laisser tomber le 'SourcePacket' et utiliser simplement' new'). Je suis allé avec '*' parce que c'était plus simple, et j'avais des problèmes avec 'new'. – outis

+0

Je suppose qu'il n'y a pas moyen de couvrir tous les cas, car les macros C++ ne sont pas capables de gérer des expressions complexes. Franchement, j'ai été surpris de pouvoir faire fonctionner ce qui précède. – outis