0
#include <iostream>
#include <fstream>

class A {
private:
  std::ostream&& out;  
public:
    A(std::ostream&& o) : out(std::move(o)) {
        out.write("test", 4);
    }
    
    void writeTest2() {
        out.write("test2", 5);
        out.flush();  // still does nothing.
    }
};

int main() {
    A a{std::ofstream{"testic"}};
    a.writeTest2();
}

When the above code is run, it creates a file named testic as expected. However, the file created contains test but not testtest2, which is obviously unexpected. What exactly is causing this behavior?

When std::ostream is taken as a lvalue reference it functions perfectly as intended.

Additional information

  • Compilers tried: clang, gcc.
  • On Platform: Linux (4.19.0-11-amd64).
tripulse
  • 1,059
  • 11
  • 18
  • *"When `std::ostream` is taken as a lvalue reference it functions perfectly as intended"* - Except you can't just substitute `&&` for `&` to take a lvalue reference. There's another change that needs to be done to the code. Ponder upon that change instead of the reference type. – StoryTeller - Unslander Monica Oct 19 '20 at 16:35
  • related/dupe: https://stackoverflow.com/questions/23892018/extending-temporarys-lifetime-through-rvalue-data-member-works-with-aggregate – NathanOliver Oct 19 '20 at 16:35
  • Hmm, seems like it @NathanOliver. – tripulse Oct 19 '20 at 16:38
  • `A(std::ostream&& o) : out(std::move(o))` doesn't move anything, since the `out` member variable is a reference being bound to passed in temporary object. – Eljay Oct 19 '20 at 17:00

1 Answers1

3

The temporary std::ofstream{"testic"} that you created only exists for the duration of the constructor call. After that it is destroyed and the file is closed, which means you are left with a reference that refers to garbage. Using that reference results in undefined behavior.

To fix it you can remove the reference all together (the && from std::ostream&& out and std::ostream&& o) and have it create a new object that is initialized from the temporary.

The above won't work because std::ostream cannot be moved. You will have to use a pointer instead if you want to maintain polymorphism. If that isn't important you can change all std::ostream&& to std::ofstream:

class A {
 private:
  std::unique_ptr<std::ostream> out;

 public:
  A(std::unique_ptr<std::ostream> o) : out(std::move(o)) {
    out->write("test", 4);
  }

  void writeTest2() {
    out->write("test2", 5);
    out->flush();
  }
};

int main() {
  A a{std::make_unique<std::ofstream>("testic")};
  a.writeTest2();
}
David G
  • 94,763
  • 41
  • 167
  • 253
  • 1
    Is ostream movable? I thought ostream was an abstract class which ofstream implements. But perhaps it's legal to convert an ofstream to an ostream since the streambuf is a separate object. – user253751 Oct 19 '20 at 16:48
  • @user253751 `ostream` has a protected move constructor so by itself it can't be moved, but the concrete stream classes (`stringstream`, `fstream`) have public move constructors allowing them to be moved. – David G Oct 19 '20 at 16:51
  • "remove the reference all together (the `&&` from `std::ostream&& out` and `std::ostream&& o`)" — removing those makes the code not compile. – tripulse Oct 19 '20 at 17:00
  • Won't the `std::ostream` need to be changed to a `std::ofstream` as well? – Eljay Oct 19 '20 at 17:02
  • @tripulse Yup, just realized that. Standby. – David G Oct 19 '20 at 17:03
  • @tripulse See my edit. You will have to use a pointer if you want to use `std::ostream` . – David G Oct 19 '20 at 17:07
  • Oh, this is all really hacky :P – tripulse Oct 19 '20 at 17:10