0

I'm trying to create an object-configurator using a fluent interface. Code:

class Configurator {
public:
    Configurator() {
        printf("Constructor.\n");
    }

    ~Configurator() {
        printf("Destructor.\n");
        printf("String: %s\n", this->str_.c_str());
    }

    Configurator& Append(const std::string& str) {
        this->str_ += str;
        return *this;
    }

private:
    std::string str_;
};

Configurator PrepareConfigurator() {
    return Configurator();
}

Configurator PrepareWithSomeAppend() {
    return PrepareConfigurator().Append("hello").Append(", ");
}

int main() {
    printf("Start.\n");
    
    PrepareWithSomeAppend().Append("world!");
    
    printf("End.\n");
    return 0;
}

Class Configurator prints string only in destructor(I want to do so that I do not force to call a helper method to call an action). Append method appends the string to the private field string. Method PrepareConfigurator creates Configurator. Method PrepareWithSomeAppend calls PrepareConfigurator and calls Append method and returns object from function. Further in main I call Append method again and I expect the output like that:

Start.
Constructor.
Destructor.
String: hello, world!
End.

But I get the output:

Start.
Constructor.
Destructor.
String: hello, 
Destructor.
String: hello, world!
End.

Why does the destructor call twice? How can I prevent it? Thanks in advance!

Edit:

I also tried to create Configurator in PrepareWithSomeAppend and the problem isn't solved. Destructor calls twice.

Configurator PrepareWithSomeAppend() {
    return Configurator().Append("hello").Append(", ");
}

Also, I tried to return a reference to Configurator from PrepareWithSomeAppend:

Configurator& PrepareWithSomeAppend() {
    return Configurator().Append("hello").Append(", ");
}

And it doesn't work too, destructor calls in PrepareWithSomeAppend method for the local object and I get segmentation fault error in main function because of reference points to the destructed object. How can I prevent calling the destructor for a local object in PrepareWithSomeAppend function? Or return an object without copying?

user1562
  • 3
  • 2
  • Your object gets copied or moved (the compiler generates a copy and/or move constructor for you - sorry did not read the detail to check which). – spectras Dec 05 '20 at 22:08
  • Nothing to worry, you don't see the other constructor call because its the copy-constructor that is used (implicitly generated). – kebs Dec 05 '20 at 22:09
  • Print out the value of `this` in your output statements, and it will give you a clue as to what is going on. You will see that `this` is different. – PaulMcKenzie Dec 05 '20 at 22:13
  • See [What is The Rule of Three?](https://stackoverflow.com/q/4172722/5987) – Mark Ransom Dec 05 '20 at 22:15
  • @MarkRansom rule of three, followed or, won't change behaviour here. Question issue here is why RVO doesn't happen here – Swift - Friday Pie Dec 05 '20 at 22:25
  • RVO does happen in `PrepareConfigurator`, but can't happen in `PrepareWithSomeAppend` because `Append` returns a reference. This results in a copy and a second object. – 1201ProgramAlarm Dec 05 '20 at 22:27
  • @Swift-FridayPie RVO **does** happen. Otherwise you would see 3 dtor calls: in `PrepareConfigurator`, `PrepareWithSomeAppend` and final temporary destruction in `main()`. However RVO can't take place for `PrepareWithSomeAppend` because it is not a direct expression - hence 2 copies and 2 dtor calls, not 1. – bloody Dec 05 '20 at 22:29
  • @bloody you just wrote what I wanted to write in answer. Yes, it happens, once, or we'd see three destructor calls. – Swift - Friday Pie Dec 05 '20 at 22:31
  • @Swift-FridayPie Glad, we have an understanding:) Please go for the answer if you wish. – bloody Dec 05 '20 at 22:37

1 Answers1

-1

To me it looks like the reason for this is the PrepareConfigurator() method, because it creates a new Configurator object. However, we return a Configurator result as well. This mean that the constructed object is copied on return, and we destroy the locally constructed object as soon as we leave the PrepareConfigurator() method.

Try to change the method so that it returns a reference or a pointer.