1

Is there a way to call a class member function automatically in the end of a scope? Apparently if the class is instantiated within the same scope, clean() can be used in the destructor of it. The question is, can we achieve that if it is instantiated before the scope? In detail:

class foo
{
public:
    void do_something() {}
    void clean() {}
};

int main()
{
    foo c;
    {
        c.do_something();
        // do something
        // call c.clean() automatically
    }
}
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
Ryan L
  • 43
  • 5
  • you could make `do` return something that when destroyed calls `clean` of the `foo` instance. Depends a bit on whom you want to protect from. A user that forgets to call `clean`, or just exceptions? – 463035818_is_not_an_ai Nov 05 '20 at 19:00
  • You can do something like that if you have another class which takes saves reference to `c` and then uses that reference to call `clean` in it's destructor. – john Nov 05 '20 at 19:01
  • `do` is not allowed as name and `;` was missing, fixed that – 463035818_is_not_an_ai Nov 05 '20 at 19:03
  • The only thing that is automatically called at a scope end is a class destructor – sedavidw Nov 05 '20 at 19:05
  • 2
    @sedavidw not true. The destructor is called when the object goes out of scope. Not at the end of any scope. i.e. `{foo c; {{}} {} {} {}}` <- 6 scopes, destructor is called only once. – JHBonarius Nov 05 '20 at 19:56
  • @JHBonarius yeah that's fair. My wording was poor. Was trying to identify that when an object instance goes out of scope, its destructor will be called – sedavidw Nov 05 '20 at 20:22
  • A BIG SIDENOTE all proposed solutions require `c::clean()` to never throw an exception, as an exception shouldn't be thrown in a destructor. – JHBonarius Nov 06 '20 at 20:39

5 Answers5

5

Something like this. May not be exactly what you are looking for as it requires you do declare another variable at the start of the scope where you want the clean function to be called.

class CleanFoo
{
public:
    CleanFoo(Foo& r) : ref(r) {}
    ~CleanFoo() { ref.clean(); }
    CleanFoo(const CleanFoo&) = delete;
    CleanFoo& operator=(const CleanFoo&) = delete;
private:
    Foo& ref;
};

int main()
{
    foo c;
    {
        CleanFoo cc(c);
        ...
    } // c.clean() will be called here
    ...
}
john
  • 85,011
  • 4
  • 57
  • 81
  • I was hoping to be able to keep the do_something() call as similar to the old code as possible, i.e., something like CleanFoo(c).do_something(), but I know with this, the instance of CleanFoo would be destroyed immediately after the line. I guess this is the best we can get. – Ryan L Nov 08 '20 at 17:58
4

std::unique_ptr actually has something like that, in the form of a second parameter you can pass to the constructor, a deleter. The deleter cleans up when the std::unique_ptr object goes out of scope. Normally (i.e. if you don't specify it) it would be std::default_delete that calls delete on the dynamically allocated memory it owns. But you can make it do other things, like this:

#include <cstdio>
#include <memory>

class foo
{
public:
    void do_something() { printf("do_something\n"); }
    void clean() { printf( "clean\n"); }
};

int main()
{
    foo c;
    {
        std::unique_ptr<foo, void(*)(foo*)> scoped_cleaner(&c, [](foo* c) { c->clean(); });
        c.do_something();
    }
}

godbolt

JHBonarius
  • 10,824
  • 3
  • 22
  • 41
2

Only thing that is called automatically at the end of the scope is the destructors of the objects whose lifetime end in that scope. As such, you could have another object local to the inner scope whose destructor calls the function.

A generic solution exists in Boost already:

foo c;
{
    BOOST_SCOPE_EXIT(&c) {
        c.clean();
    };
    c.do_something();
    // do something
}

One caveat with all these answers including mine is that foo::clean() must not throw.

eerorika
  • 232,697
  • 12
  • 197
  • 326
1

Write a do_guard. You can leverage RAII to create a wrapper that will call do on the object you give the wrapper in it's constructor, and then the destructor will call clear. That would look like

struct do_guard
{
    foo& ref;
    do_guard(foo& ref) : ref(ref) { ref.do_something(); }
    ~do_guard() { ref.clean(); }
};

int main()
{
    foo c;
    {
        do_guard guard(c);
        // do something
    } // guard.~do_guard() will call c.clean() automatically
}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
0

You can make do_something return something that gets its destructor called at the end of the scope:

class foo;
struct cleaner {
    foo& f;
    cleaner(foo& f) : f{f} {}
    ~cleaner();
};


class foo
{
public:
    cleaner do_something() { return *this; }
    void clean() {}
};

cleaner::~cleaner() { f.clean(); }

int main()
{
    foo c;
    {
        auto x = c.do_something();
        // do something           
    }  // x calls c.clean() automatically
}
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • I would add a `[[nodiscard]]` because if you accidentally forget to assign the return value, `clean` will be called immediately... – JHBonarius Nov 05 '20 at 19:59