11

I've been experimenting with C++, and I've come across a problem that I don't know how to solve.

Basically, I've discovered that you can't copy streams (see Why copying stringstream is not allowed?), and that also applies for objects that 'wrap' them. For example:

  • I create a class with a data member of type stringstream.
  • I create an object of this class.
  • I attempt to copy the object, eg "TestObj t1; TestObj t2; t1 = t2;"

This causes the error C2249:

'std::basic_ios<_Elem,_Traits>::operator =' : no accessible path to private member declared in virtual base 'std::basic_ios<_Elem,_Traits>'

So my question is: how can I (preferably easily) copy objects that have data members of type *stream?

Full example code:

#include <iostream>
#include <string>
#include <sstream>

class TestStream
{
public:
    std::stringstream str;
};

int main()
{
    TestStream test;
    TestStream test2;
    test = test2;

    system("pause");
    return 0;
}

Thanks in advance.

UPDATE

I've managed to solve this problem thanks the answers below. What I have done is declare the stream objects once and then simply reference them using pointers in the wrapper objects (eg, TestStream). The same goes for all other objects that have private copy constructors.

Community
  • 1
  • 1
Matt Larsen
  • 471
  • 4
  • 6
  • 12

5 Answers5

6

The reason you are not allowed to copy a stream is that it doesn't make sense to copy a stream. If you explain what it is that you are trying to do, there's certainly a way to do it. If you want a chunk of data you can copy, use a string. But a stream is more like a connection than a string.

Community
  • 1
  • 1
David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • 2
    What if I want to copy _the connection_? I.e., I'm reading a file, and I reach a line I know I'll have to read later (or group of lines to large to store). It would be convenient to copy the stream, so that the copy is at that location in the stream, and then be able to come back to it later. – VF1 Aug 21 '13 at 17:40
  • 1
    @VF1 That doesn't make sense in general. That might make sense for some kinds of streams, but as a general feature of a stream, it's not sensible. – David Schwartz Aug 21 '13 at 18:48
  • I think I found what I need (yes, it was for istreams only) - storing the the current stream position with `tellg` and using `seekg` later. – VF1 Aug 22 '13 at 00:17
  • @VF1 Pay attention that seekg() and seekp() move position sometimes jointly and sometimes not, depending on what type of stream you use. – StanE Jun 15 '16 at 06:53
3

This article provides ways to do it. Note however the interesting summary:

In summary, creating a copy of a stream is not trivial and should only be done if you really need a copy of a stream object. In many cases, it is more appropriate to use references or pointers to stream objects instead, or to share a stream buffer between two streams.

JRL
  • 76,767
  • 18
  • 98
  • 146
1

Certainly you have to write the copy constructor and copy assignment operator yourself.

Next, you have to decide what semantics you would like the copy to have. So:

TestStream test;
TestStream test2;
test2 << "foo"
test = test2;
test << "bar";

test2.str.str(); // should this be "foo" or "foobar" ?

If you want a shallow copy, ("foobar") then you need to share the stringstream object between multiple instances of TestStream, probably best to use a shared_ptr for that.

If you want a deep copy ("foo"), then you could copy like this:

TestStream(const TestStream &rhs) : str(rhs.str.str()) {}

Or use one of the variants in the question you link to.

That covers a stringstream to which you're in the middle of writing when you take the copy. If you're in the middle of reading from it, or if you're writing but you might not be writing to the end because of use of seekp, then you need to capture the current read/write positions as well as the data in the stringstream, which you do with tellg/tellp.

You might also want to copy across the stream's format state, and so on, which is what copyfmt does, and even the error flags (rdstate -- copyfmt leaves them alone).

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
0

To test the performance of various write operations in c++ here is a code which compiles on your machine and tests write operations with and without buffering with several methods:

Link

#include <stdio.h>
#include <cstring>
#include <iostream>
#include <fstream>
#include <chrono>

#define TOCOUT(output) \
    if(!outputToCout) { \
        buf = output##_t.rdbuf(); \
    } else { \
        buf = std::cout.rdbuf(); \
    } \
    std::ostream output(buf);

void fstreamBufferTest(){

    const bool outputToCout = true;

    const unsigned int multiplyStep = 1<<2;
    const unsigned int startLength = 1<<2;
    const unsigned int stopLength = 1<<24;

    const unsigned int writeNTimes = 1; // Averaging over some many times!
    const unsigned int fileLength = 1<< 30; //104857600=100mb,  314572800=300mb , 1<< 30 =1GB
    std::string add = "1000.txt";
    unsigned int loops, restBytes;


    std::streambuf * buf;

    std::ofstream output1_t("FStreamTest-FstreamBuffering-OwnBufferSet-"+add);
    TOCOUT(output1);
    output1 << "#Buffer Length \tTimeToWrite \tWriteSpeed [mb/s]" << std::endl;

    std::ofstream output2_t("FStreamTest-ManualBuffering-StdStreamBuffer-"+add);
    TOCOUT(output2);
    output2 << "#Buffer Length \tTimeToWrite \tWriteSpeed [mb/s]" << std::endl;

    std::ofstream output3_t("FStreamTest-ManualBuffering-NoInternalStreamBuffer-"+add);
    TOCOUT(output3);
    output3 << "#Buffer Length \tTimeToWrite \tWriteSpeed [mb/s]" << std::endl;

    std::ofstream output4_t("FStreamTest-NoManualBuffering-NoInternalStreamBuffer-"+add);
    TOCOUT(output4);
    output4 << "#Buffer Length \tTimeToWrite\tWriteSpeed [mb/s]" << std::endl;

    std::ofstream output5_t("FStreamTest-NoManualBuffering-StdStreamBuffer-"+add);
    TOCOUT(output5);
    output5 << "#Buffer Length \tTimeToWrite \tWriteSpeed [mb/s]" << std::endl;

    // To Cout


    typedef std::chrono::duration<double> fsec;
    typedef std::chrono::high_resolution_clock Clock;



    // Test Data for the Buffer
    bool removeFile = true;
    char value = 1;
    char *testData = new char[fileLength]; // Just Garbage 1GB!!
    std::memset(testData,value,fileLength);

    // Preallocate file;
    if(!removeFile){
        std::fstream stream;
        stream.open("test.dat", std::ios::binary | std::ios::trunc | std::ios::out);
        for(int i = 0; i < writeNTimes; i++){
                stream.write(testData, fileLength );
        }
        stream.close();
    }else{
        if( remove( "test.dat" ) == 0){
            std::cout << "File deleted at start!" << std::endl;
        }
    }

    for(unsigned int bufL = startLength; bufL <= stopLength; bufL = bufL * multiplyStep){

        // First Test with Fstream Buffering!
        {
            std::cout << "Doing test: FStream Buffering: " << bufL <<std::endl;
            char * buffer = new char[bufL];
            //open Stream
            std::fstream stream;
            stream.rdbuf()->pubsetbuf(buffer, bufL);
            stream.open("test.dat", std::ios::binary | std::ios::trunc | std::ios::out);

            // Write whole 1gb file! we have fstream buffering the stuff
            auto t1 = Clock::now();
            for(int i = 0; i < writeNTimes; i++){
                stream.write(testData, fileLength );
            }
            stream.close();
            auto t2 = Clock::now();

            //Calculate timing
            fsec time = (t2 - t1) / writeNTimes;
            output1 << bufL << "\t" << time.count() <<"\t" << (fileLength/time.count()) / (1024*1024) << std::endl;

            delete buffer;
            if(removeFile){
                if( remove( "test.dat" ) != 0){
                    std::cerr << "File not deleted" << std::endl;
                };
            }
        }

        // Second Test with Manual Buffering!
        {
            std::cout << "Doing test: Manual Buffering: " << bufL <<std::endl;
            // Calculate the loops to write fileLength
            loops = fileLength / bufL;
            restBytes =  fileLength % bufL;

            //open Stream
            std::fstream stream;
            stream.open("test.dat", std::ios::binary | std::ios::trunc | std::ios::out);
            // TODO stream buf -> 0

            // Write 1GB File in loops of bufL
            auto t1 = Clock::now();
            for(int i = 0; i < writeNTimes;  i++){
                for(int i = 0; i < loops; i++){
                   stream.write(testData, bufL );
                }
                stream.write(testData, restBytes );
            }
            stream.close();
            auto t2 = Clock::now();

            //Calculate timing
            fsec time = (t2 - t1) / writeNTimes;
            output2 << bufL << "\t" << time.count() <<"\t" << (fileLength/time.count()) / (1024*1024) << std::endl;
            if(removeFile){
                if( remove( "test.dat" ) != 0){
                    std::cerr << "File not deleted" << std::endl;
                };
            }
        }

        // Second Test with Manual Buffering!
        {
            std::cout << "Doing test: Manual Buffering (no internal stream buffer): " << bufL <<std::endl;
            // Calculate the loops to write fileLength
            loops = fileLength / bufL;
            restBytes =  fileLength % bufL;

            //open Stream
            std::fstream stream;
            stream.open("test.dat", std::ios::binary | std::ios::trunc | std::ios::out);
            stream.rdbuf()->pubsetbuf(0, 0);

            // Write 1GB File in loops of bufL
            auto t1 = Clock::now();
            for(int i = 0; i < writeNTimes;  i++){
                for(int i = 0; i < loops; i++){
                   stream.write(testData, bufL );
                }
                stream.write(testData, restBytes );
            }
            stream.close();
            auto t2 = Clock::now();

            //Calculate timing
            fsec time = (t2 - t1) / writeNTimes;
            output3 << bufL << "\t" << time.count() <<"\t" << (fileLength/time.count()) / (1024*1024) << std::endl;
            if(removeFile){
                if( remove( "test.dat" ) != 0){
                    std::cerr << "File not deleted" << std::endl;
                };
            }
        }


        {
            std::cout << "Doing test: No manual Buffering (no internal stream buffer): " << bufL <<std::endl;
            // Calculate the loops to write fileLength
            loops = fileLength / bufL;
            restBytes =  fileLength % bufL;

            //open Stream
            std::fstream stream;
            stream.open("test.dat", std::ios::binary | std::ios::trunc | std::ios::out);
            stream.rdbuf()->pubsetbuf(0, 0);

            // Write 1GB File in loops of bufL
            auto t1 = Clock::now();
            for(int i = 0; i < writeNTimes;  i++){
                stream.write(testData, fileLength );
            }
            stream.close();
            auto t2 = Clock::now();

            //Calculate timing
            fsec time = (t2 - t1) / writeNTimes;
            output4 << bufL << "\t" << time.count() <<"\t" << (fileLength/time.count()) / (1024*1024) << std::endl;
            if(removeFile){
                if( remove( "test.dat" ) != 0){
                    std::cerr << "File not deleted" << std::endl;
                };
            }
        }

        {
            std::cout << "Doing test: No manual Buffering (std stream buffer): " << bufL <<std::endl;
            //Calculate the loops to write fileLength
            loops = fileLength / bufL;
            restBytes =  fileLength % bufL;

            //open Stream
            std::fstream stream;
            stream.open("test.dat", std::ios::binary | std::ios::trunc | std::ios::out);

            // Write 1GB File in loops of bufL
            auto t1 = Clock::now();
            for(int i = 0; i < writeNTimes;  i++){
                stream.write(testData, fileLength );
            }
            stream.close();
            auto t2 = Clock::now();

            //Calculate timing
            fsec time = (t2 - t1)/ writeNTimes;
            output5 << bufL << "\t" << time.count() <<"\t" << (fileLength/time.count()) / (1024*1024) << std::endl;
            if(removeFile){
                if( remove( "test.dat" ) != 0){
                    std::cerr << "File not deleted" << std::endl;
                };
            }
        }



    }



}

int main() {
fstreamBufferTest();
}
Gabriel
  • 8,990
  • 6
  • 57
  • 101
0

There are two things you can do, both involve being careful about who owns the object:

  1. Store a reference to a stream, and make sure the object does not go out of scope as long as these classes of yours are around.

  2. copy the pointers around, and be sure to delete only when the last of your classes is done with the stream object pointed to.

Both are equivalent, although I personally prefer the reference approach.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • If you go for the 2nd and have a compiler capable of C++11 you could use smart pointers. If you don't use such a compiler you could use the boost library or write a smart pointer class yourself. – Darokthar Oct 26 '11 at 14:19
  • 3
    @Darokthar agreed. This is one case where `boost::shared_ptr` is appropriate (and definitely preferable to any of the alternatives). – James Kanze Oct 26 '11 at 14:24