C++ copy a stream object

2020-01-27 07:57发布

问题:

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.

回答1:

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.



回答2:

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.



回答3:

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).



回答4:

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.



回答5:

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();
}