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.
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.
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.
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).
There are two things you can do, both involve being careful about who owns the object:
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.
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.
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();
}