user18476
user18476

Reputation: 39

How do I call a function only once per value in C++?

I'm looking for an easy-to-use macro for calling a function only once for a specific value. For example:

void foo( Object* obj )
{
   // Print out the name of each object only once
   DO_ONCE( obj, printf("This gets printed only once per object! %s\n",obj->GetName()) );
}

Then

Object obj1("obj1Name"),obj2("obj2Name");
foo(&obj1);
foo(&obj1);
foo(&obj2);

Should print out

This gets printed only once per object! obj1Name
This gets printed only once per object! obj2Name

Upvotes: 2

Views: 1797

Answers (6)

outmind
outmind

Reputation: 829

Concept:

template<class t1> void do_once(t1* obj) {
    static std::map<t1*,bool> was_here;
    if (was_here[obj]==false) was_here[obj]=true, throw obj;
}

void ff(int * x)
{
    try {do_once(x); } catch (int* obj) { printf("First time: %d\n",*obj);}
}

Upvotes: 0

Antti Huima
Antti Huima

Reputation: 25512

#include <set>

...

#define DO_ONCE(type, val, stmt) \
do \
{ \
    type __tmp = (val); \
    static std::set < type > __memo; \
    if (__memo.find(__tmp) == __memo.end()) \
    { \
        __memo.insert(__tmp); \
        do { stmt; } while(0); \
    } \
} \
while(0)

...

DO_ONCE(Object *, obj, printf(...));

Upvotes: 1

Konrad Rudolph
Konrad Rudolph

Reputation: 545508

Put your objects in a container and filter/group so each one only appears once. This can be done trivially by using set (or std::tr1::unordered_set) as the container for your objects. This effectively makes them unique. You can then iterate over the container.

Or, as others have proposed, use the container inside the function as a memoization device. However, in general I think explicitly grouping the results may be more appropriate.

Upvotes: 3

SingleNegationElimination
SingleNegationElimination

Reputation: 156138

You'll probably need to memoize the objects. Something linke

bool do_once( Object * obj )
{
    static std::set<Object*> memo; 
    if ( memo.count(obj) )
    {
        memo.insert(obj);
        return true;
    }
    return false;
}
#define DO_ONCE(o,a) (do_once(obj) && a)

Upvotes: 1

Mykola Golubyev
Mykola Golubyev

Reputation: 59804

#include <iostream>
#include <ostream>
#include <set>
#include <string>

class Object
{
public:
    Object( const std::string& name ):
        name_( name )
    {}
    std::string GetName() const
    {
        return name_;
    }
private:
    std::string name_;
};

void print( Object* object )
{
    std::cout << object->GetName() << std::endl;
}

template <typename T, typename TFunction>
void doOnce( T* object, TFunction function )
{
    static std::set<T*> objectsThatWasThere;

    if ( objectsThatWasThere.end() == objectsThatWasThere.find( object ) )
    {
        function( object );
        objectsThatWasThere.insert( object );
    }
}

int main()
{
    Object obj1("Test");
    Object obj2("The");

    doOnce( &obj1, print );
    doOnce( &obj1, print );
    doOnce( &obj1, print );
    doOnce( &obj2, print );
    doOnce( &obj2, print );
    doOnce( &obj2, print );

    return 0;
}

Upvotes: 1

Johannes Schaub - litb
Johannes Schaub - litb

Reputation: 506847

I prefer a map

void foo( Object* obj ){
    // Print out the name of each object only once 
    static std::map<Object*, size_t> calls;
    if(calls[obj] == 0) {
        std::cout << "This gets printed only once per object! "
                  << obj->GetName();
        calls[obj] = 1;
    }
}

You may also decide to increment the counter if you want to count the calls too. But note that it is also not really fail-safe. If you delete an object, and then new it again and it happens to get the same address, it will be assumed to be already printed.

Upvotes: 2

Related Questions