std::ostream protected move constructor?

Hi,
I'm creating a logger system and I would like to have a AddLogStream function, which i can then use to hand over ownership of an output stream to the logger (which the logger will then write to whenever something gets logged).

so something like this:
1
2
3
4
std::ofstream file("log.txt");
Logger::AddLogStream( std::move(file) );

Logger::Log("test\n");


and i just want to store the ostreams in a vector like this:
1
2
3
4
5
6
7
8
9
10
11
12
std::vector< std::ostream > LogStreams;

void AddLogStream( std::ostream&& stream )
{
  LogStreams.push_back( std::move(stream) ); //Error
}

void Log(const char* msg)
{
  for(std::ostream& stream : LogStreams)
    stream << msg;
}


But this doesn't compile.
When i looked into the issue i found out that its because std::ostream's move constructor is protected for some reason?
Does anyone know why exactly?
& how can i get around this?

thank you
Last edited on
The "exactly" part is quite a winding yarn of a tale, but the short version goes like this:

At one time they were public. At some point years later it was realized certain very strange behavior resulted from using move constructors on such stream objects, and it is rather technically related to how both ostreams themselves are built, and how they are the base class for a variety of other streams.

There are discussions in public view going back over a decade on the subject, and the engineers who built this design were not at all clear even among them what to do or how and why, until serious time was spent reasoning it all out, and thinking through it all.

Many of them were not sure for quite a while.

You can't get around it.

If you try, you'll open up the same can of worms they sweated through over a decade ago, and reach a similar conclusion.

One could fairly argue the design is flawed. The problem is really that the design may be too extensible for this kind of feature to be safely implemented, and so the best solution known so far is to protect the move constructor (and a few other such things) for use only be derived classes than understand how to use them effectively.

That said, what you can do is wrap the stream into a std::shared_ptr (or std::unique_ptr if you move) and pass it that way.

What I get from the reading (which is now, essentially, historical documentation on the engineer's thought process) - this fact (using shared_ptr or unique_ptr to wrap around the stream object) meant "solving" the problem was actually not required, since a simpler solution was otherwise available.
Last edited on
you are basically trying to do Base b = Derived();
that's object slicing.

you may work directly with ofstream object instead
std::vector< std::ofstream > LogStreams;
and open "/dev/stdout" if you want to add the console to your logger.


if perhaps you simply want to tee https://stackoverflow.com/questions/999120/c-hello-world-boost-tee-example-program/999218#999218
Last edited on
So my question is: How come std::ostream doesn't have a public move constructor?

Some of the standard streams rdbuf() member function returns a stored pointer whose value is offset into the same stream. This makes the rdbuf pointer, and therefore the whole ostream immovable in general.

For whatever reason, the decision was to remove move and swap from the public interface instead of providing polymorphic behavior.
See LWG issue 911: https://wg21.link/lwg911

For weak evidence, see this program:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <sstream>
#include <iostream>

int main()
{
    std::stringstream s; 
    
    std::cout 
      << std::hex
      << "     sizeof(s): 0x" << sizeof(s) << '\n'
      << "            &s: "   << ((void*)&s) << '\n'
      << "s.rdbuf() - &s: 0x"   
      << (reinterpret_cast<char*>(s.rdbuf()) - 
          reinterpret_cast<char*>(&s)) << '\n';
}
     sizeof(s): 0x188
            &s: 0x7ffd9ae781d0
s.rdbuf() - &s: 0x18

(Stronger evidence could be obtained by reading the library code).
http://coliru.stacked-crooked.com/a/4913767f3a28ef99


Last edited on
mbozzi wrote:
For whatever reason, the decision was to remove move and swap from the public interface instead of providing polymorphic behavior.

How would you suggest that work? Move constructor can't be virtual. The design point here is that only the most-derived type owns the associated streambuf and therefore knows how to set/move/clone it. Therefore only the derived type has the information necessarly to correctly move the base, therefore the base move ctor must be protected.
std::ostream, by design, is intended to serve as an abstract base class. You should normally never try or need to create standalone objects of std::ostream type. This is why majority of its constructors are protected.

std::vector< std::ostream > is already a clear indication that you are doing something meaningless.
Last edited on
How would you suggest that work?

It wouldn't, never mind.

Initially, I thought ostream could be made to own its streambuf. If that could be done, ostream could store a unique_ptr to a streambuf, then move/swap the pointers.
Last edited on
@mbozzi, you're in the right neighborhood, and such thought was considered back in the day. The original designers did consider a wrapper of sorts, but realized that the idea generally resolved into the same thing as using unique_ptr in application code, then decided to just stop, declare the related constructors and operators protected and move on.
Topic archived. No new replies allowed.