2010-12-13 10 views
8

Dato il seguente:Posso scrivere un functor C++ che accetta sia un puntatore raw sia un puntatore intelligente?

struct Foo 
{ 
    int bar() const; 
}; 

struct IsEqual : public std::unary_function<Foo*, bool> 
{ 
    int val; 
    IsEqual(int v) : val(v) {} 

    bool operator()(const Foo* elem) const 
    { 
     return elem->bar() == val; 
    } 
}; 

Ho un contenitore di Foo* e io uso std::find_if e std::not1 per scoprire se ci sono elementi nel contenitore dove bar() ritorna qualcosa di diverso da un dato valore. Il codice simile a questo:

// Are all elements equal to '2'? 
bool isAllEqual(const std::vector<Foo*> &vec) 
{ 
    return find_if(vec.begin(), vec.end(), std::not1(IsEqual(2))) == vec.end(); 
} 

Avanti veloce verso il futuro ed ora ho un contenitore diverso, questa volta contenente std::tr1::shared_ptr<Foo>. Mi piacerebbe semplicemente riutilizzare il mio functor in una versione sovraccaricata di isAllEqual(). Ma non posso. Foo* e shared_ptr<Foo> sono diversi tipi. E ho bisogno di ereditare da unary_function così posso usare not1. Sarebbe più elegante se potessi evitare di scrivere lo stesso funtore due volte.

Domande:

  • C'è un modo di scrivere IsEqual quindi è possibile utilizzare sia i puntatori prime e intelligente?
  • Mi sono ammanettato utilizzando std::not1? Dovrei semplicemente scrivere IsNotEqual?

Restrizioni:

  1. non posso usare qualsiasi cosa dalla libreria spinta.
  2. Il nostro compilatore non è abbastanza bello da supportare C++ 0x lambda.
+1

Questo suona come un esempio in cui i modelli sarebbero belli. – GWW

+0

@Kristo: Il tuo compilatore è abbastanza interessante da fornire altri contenuti in C++ 0x, come 'std :: begin'? –

+0

@ Ben, stiamo usando gcc 4.1.2, quindi probabilmente no. 'std :: begin' e' std :: end' dovrebbero essere banali da scrivere però. –

risposta

2
// --*-- C++ --*-- 

#include <vector> 
#include <algorithm> 
#include <iostream> 

// Template unary function example. 
template <typename T> 
struct IsEqual : public std::unary_function<T, bool> 
{ 
    int v; 

    IsEqual (int v) : v (v) {} 

    bool operator() (const T & elem) const 
    { 
     return elem ? elem->bar() == v : false; 
    } 
}; 

// Generic algorithm implementation example... 
template <typename T1, typename T2> 
bool isAllEqual (const T1 & c, T2 v) 
{ 
    return find_if (
     c.begin(), c.end(), 
     std::not1 (IsEqual <typename T1::value_type> (v))) == c.end(); 
} 

// Some arbitrary pointer wrapper implementation, 
// provided just for an example, not to include any 
// specific smart pointer implementation. 
template <typename T> 
class WrappedPtr 
{ 
    const T *v; 

public: 
    typedef void (WrappedPtr<T>::*unspecified_boolean_type)() const; 

    WrappedPtr (const T *v) : v (v) {} 

    const T *operator ->() const { return v; } 

    operator unspecified_boolean_type() const 
    { 
     return v != NULL ? 
      &WrappedPtr<T>::unspecified_boolean_true : NULL; 
    } 

private: 
    void unspecified_boolean_true() const {} 
}; 

// Example of structure that could be used with our algorithm. 
struct Foo 
{ 
    int v; 

    Foo (int v) : v (v) {} 

    int bar() const 
    { 
     return v; 
    } 
}; 

// Usage examples... 
int main() 
{ 
    Foo f1 (2), f2 (2); 

    // Example of using raw pointers... 
    { 
     std::vector<Foo *> vec; 
     vec.push_back (NULL); 
     vec.push_back (&f1); 
     vec.push_back (&f2); 

     if (isAllEqual (vec, 2)) 
      std::cout << "All equal to 2" << std::endl; 
     else 
      std::cout << "Not all equal to 2" << std::endl; 
    } 

    // Example of using smart pointers... 
    { 
     std::vector< WrappedPtr<Foo> > vec; 
     vec.push_back (NULL); 
     vec.push_back (&f1); 
     vec.push_back (&f2); 

     if (isAllEqual (vec, 2)) 
      std::cout << "All equal to 2" << std::endl; 
     else 
      std::cout << "Not all equal to 2" << std::endl; 
    } 
} 
+0

+1 per il controllo di un puntatore nullo in 'operator()'. Mi sembra buono. –

+0

@Vlad: non funziona con i vecchi ordinari matrici. Inoltre, è una buona idea che 'unary_function :: Arg' sia diverso dal tipo di parametro' operator()() '? –

+0

@ Ben: Penso che per riferimento costante allo stesso tipo sia OK. Per supportare gli array, suppongo che per gli array semplici devi avere una specializzazione diversa come il modello void foo (const (& array) [Len]) ... o qualcosa del genere. –

2

mio colpo sarebbe qualcosa di simile a questo:

template<typename PtrToFoo> 
struct IsEqual : public std::unary_function<PtrToFoo, bool> 
{ 
    int val; 
    IsEqual(int v) : val(v) {} 

    bool operator()(PtrToFoo elem) const 
    { 
     return elem->bar() == val; 
    } 
}; 

Avrai un diverso operator() di istanze per tutto dereferencable con ->, puntatori in modo crudo e puntatori intelligenti.

+0

Umm, su quella classe base ... –

+0

Puoi farlo? Ho pensato che il primo argomento template a 'unary_function' doveva corrispondere al tipo di argomento di' operator() '. –

+0

No, non puoi. Sì, lo fa. –

8

ne dite:

template<typename T> 
struct IsEqual : public std::unary_function<const T&, bool> 
{ 
    int val; 
    IsEqual(int v) : val(v) {} 

    bool operator()(const T& elem) const 
    { 
     return elem->bar() == val; 
    } 
}; 

template<typename T> 
IsEqual<T> DeduceEqualityComparer(int v, T) { return IsEqual<T>(v); } 

// Are all elements equal to '2'? 
template<typename TContainer> 
bool isAllEqual(const TContainer& coll) 
{ 
    using std::begin; // in C++0x, or else write this really simple function yourself 
    using std::end; 
    if (begin(coll) == end(coll)) return true; 
    return find_if(begin(coll), end(coll), std::not1(DeduceEqualityComparer(2, *begin(coll)))) == end(coll); 
} 
+0

Hmmm, mi piace. Tuttavia, non è molto meno digitante di 'IsEqual >' (e quindi non dovrei scrivere il deduttore). +1 comunque. –

+0

@Kristo: Ok, ma ora anche 'isAllEqual' è un template, codice in arrivo. –

+0

Si potrebbe anche fornire a 'IsEqual' un argomento template predefinito:' template struct IsEqual ... 'E poi continua a usare' IsEqual' come prima per gli iteratori su 'Foo *'. – aschepler

1

Si potrebbe forse fare qualcosa di complicato con le conversioni implicite:

class IsEqualArg { 
public: 
    // Implicit conversion constructors! 
    IsEqualArg(Foo* foo) : ptr(foo) {} 
    IsEqualArg(const std::tr1::shared_ptr<Foo>& foo) : ptr(&*foo) {} 
private: 
    Foo* ptr; 
    friend struct IsEqual; 
}; 

struct IsEqualArg : public std::unary_function<IsEqualArg, bool> { 
    bool operator()(const IsEqualArg& arg) const; 
    //... 
}; 

Ma mi piacerebbe molto piuttosto appena scritto un IsNotEqual.

0

La risposta di Ben è davvero l'unica cosa che puoi fare in C++ 03. In C++ 0x e/o con boost :: bind, non è necessario ereditare da unary_function. Questo ti permette di usare un operatore template(). Di solito riesci a fare lo stesso con C++ 03, ma penso che sia tecnicamente scorretto farlo.

+0

TR1 'bind' potrebbe essere ok. Dovrei controllare con le persone che lavorano con il nostro codice. E non è sbagliato scrivere un operatore di tipo '()'. È l'uso di 'not1' che è il punto critico. –