Search code examples
c++oopdestructorfluent-interface

Immediately calling destructor after setting in the fluent interface in C++


I want to create a class only for setting params for function. I'm using a fluent interface for that. Some function returns the object to setting params and real code will execute in the destructor of this object.

First of all, is it a good idea? Is this a common way to improve code readability when I have a lot of parameters? Or maybe I should use another way to do that? I would be glad to advise on this.

If it's okay, how can I prevent accidentally saving an object to a local variable? In this case, the destructor will be called at end of the variable scope.

Example:

class MyClass {
public:
    ~MyClass() noexcept {
        // some code to execute immediately after setting params A and B
    }

    // abstract setting methods
    MyClass &SetA() { return *this; }
    MyClass &SetB() { return *this; }
};

MyClass SomeFunction() {
    return MyClass();
}

int main() {
    // Correct usage:
    SomeFunction()
            .SetA()
            .SetB();
    // destructor here

    // I want to prevent it:
    auto my_class = SomeFunction();
    my_class
            .SetA()
            .SetB();

    {
        auto my_class2 = SomeFunction();
        my_class2
                .SetA()
                .SetB();
        // destructor of `my_class2` here
    }

    // destructor of `my_class` here.
    return 0;
}

Solution

  • In general the destructor is a good place if you want force that something happens even if an exception occured (but be aware that destructros are declared noexcept(true) by default).

    I personally use it for "finally" blocks (with noexcept(false)). But usually in C++ you don't need finally because of RAII.

    I think you can't prevent the life-time extension / local variables. And we have no attribute [[discard]], only [[nodiscard]].

    But why you don't do the work in SetA()? For me it would be more clear?

    A good reason would be if you need all the param data to do the work...

    Update

    There is one option. You could make your object not copyable / moveable:

        MyClass() = default;
        MyClass(const MyClass&) = delete;
        MyClass(MyClass&&) = delete;
    

    But with such approach you can't have something like 'SomeFunction'. And of course such a factory function might causes a copy which call your destructor, too.


    As a suggestion I would simply do it explicit:

    SomeFunction()
                .SetA()
                .SetB()
                .Exec()
    ;