简体   繁体   中英

Member-Function-Scoped Variable

Consider the following code:

#include <iostream>

struct X {
    int foo() {
        // Can I get this to be an instance-specific static variable, please?
        static int i = 0;
        return i++;
    }
};

int main() {
    X a, b;
    std::cout << a.foo() << ' ';
    std::cout << b.foo() << ' ';
    std::cout << b.foo() << '\n';
    // output is: 0 1 2
    // desired output: 0 0 1
}

Is it possible to get a copy of this static variable i for each instance of X without the need to move the declaration and initialization to some far, far away headers and constructors?

The reason I want this, is because the value of this variable is only relevant within this specific function (but also specific to the instance whose member function it is), eg, last call parameters, time of last call etc.

The class that stands behind this idea is already somewhat large and spreading the declaration and the initialization of such tiny variables that are used within one single function is getting ugly.

Update: Note that I don't want to leak memory. When an instance is destroyed, the variables associated with it should be removed as well.

Update²: Apparantly (and unfortunately) there is indeed no proper language feature with this exact semantics. While there are some workarounds, each of them introduces limitations and pitfalls when it comes to

  • placement of "function" declaration and definition
  • accessing other "real" member variables
  • overloading the "function" in a derived class
  • ...

With these implications in mind it just appears most efficient to stick with the first thing that comes to mind:

struct A {
    int j = 0;
    int i = 0;
    int foo() { return i++ + j++; }
};

rather than going for something like this:

struct B {
    int j = 0;
    std::function<int()> foo = 
        [this, i = 0]() mutable { return i++ + this->j++; };
};

or this:

struct C {
    int j;
    struct Foo {
        int i; 
        C *c;
        Foo(C *c) : i(), c(c) {}
        int operator() () { return i++ + c->j++; }
    } foo;
    C() : j(), foo(this) {}
};

or this:

struct D {
   int j = 0;
   std::map<std::string, int> i;
   int foo() { return i[__PRETTY_FUNCTION__]++ + j++; }
};

or similar.

Many thanks for your comments and answers!

No, it's not possible.

Two alternative options are available to you:

  1. Make the variable a member, which is the language feature with the semantics you want;
  2. or invent a new language.

I do understand your concern, but it really only stems from your class apparently being too large . Separate its responsibilities.

My turn:

struct X {
    class _foo {
        int i;
    public:
        _foo() : i(0) { }
        int operator()(void) {
            return i++;
        }
    } foo;
};

Basically, function-static variable makes the function an object (identity, state, behavior). You just don't want it to be singleton. So here it is - a class.

Probably this is what you want:

struct X {
    X() : i(0) {}  // initialize variable on construction

    int foo() {
        // Can I get this to be an instance-static variable, please?
        return i++;
    }

    int i; // instance variable
};

Edit: Alternative without member variable, for those who don't look for simple options:

typedef std::map<X*,int> XMap;
static XMap xMap;

struct X {
    X() { xMap.insert(this, 0); }
    ~X() { xMap.erase(this); }

    int foo() {
        return xMap[this]++;
    }
};

Edit: same as above, but without constructor/destructor:

struct X {
    int foo() {
        return xMap[this]++;  // same as below:
        // XMap::iterator it = xMap.find(this);
        // if (it == xMap.end())
        // {
        //     it = xMap.insert(XMap::value_type(this, 0)).first;
        // }
        // return *it++;
    }
};

You can encapsulate the function state in a lambda, contained in a std::function member:

#include <functional>
#include <iostream>
struct X {
    std::function<int()> foo = [i = 0]() mutable { return i++; };
};
int main() {
    X a, b;
    std::cout << a.foo() << " " << b.foo() << " " << b.foo() << std::endl;
}

Note that this uses lambda generalized capture, a C++14 feature but already supported by g++ (since 4.7.2 at least). Otherwise, you can manually rewrite the lambda into a (more efficient) functor:

#include <iostream>
struct X {
    struct { int i = 0; int operator()() { return i++; } } foo;
};
int main() {
    X a, b;
    std::cout << a.foo() << " " << b.foo() << " " << b.foo() << std::endl;
}

Is foo the only method of X ? It seems you just want a convenient way to create counters then:

#include <functional>

std::function<int()> create_counter()
{
    int i = 0;
    return [=]() mutable { return i++; };
}

#include <iostream>

int main()
{
    auto a = create_counter();
    auto b = create_counter();

    std::cout << a() << '\n';
    std::cout << a() << '\n';
    std::cout << b() << '\n';
    std::cout << b() << '\n';
    std::cout << b() << '\n';
    std::cout << b() << '\n';
    std::cout << a() << '\n';
}

Hint: You'll get much better answers if you refrain from using names such as X and foo ;-)

You could create a method GetVariableI(X) that would accept an instance of X and return an instance-specific value, or a static value when null is passed.

That approach should solve your problem, however it is silly. It does what you say you want, but almost for sure not what you need. What is the behaviour you're trying to achieve? If you could elaborate on what you're trying to achieve, we could offer an alternative (and sane) solution.

You might look at Literate Programming by Knuth. It did stuff like this. Basically you want a macro language that lets you move around text.

Extending, and slightly obfuscating, Valeri Atamaniouk's answer, this seems to do the trick without the need for any member variables.

struct X {
    int foo() {
        struct l_int {
            int i;
            l_int():i(0) {};
        };
        static std::map<X*,l_int> i_map;
        return (i_map[this]).i++;
    }
};

I don't condone it as I think it's horrible, and would probably fall over when you tried copying objects around, but there you go. Also, yes, it does leak a little memory each time you destroy an object.

The reason for storing the value in a struct is that you can be sure it will be initialised to zero when you create a new entry in the std::map , which will happen for each new object on calling foo() .

This program gives me 0 1 0 as an output, but I refer to my comment above about the order of evaluation.

Here is another option. Isn't very clean conceptually, but it does handle the technical issue of putting your function and your variable closer together, while staying fairly simple:

struct XFoo {
    XFoo() : i(0) { }

    int foo() { return i++; }

    private:
        int i;
};

struct X : XFoo {
  // lots of stuff here
};

or with C++11:

struct XFoo {
    int foo() { return i++; }

    private:
        int i = 0;
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM