StackedCrooked
StackedCrooked

Reputation: 35485

Redirecting std::cout

I need a class that redirects one ostream to another ostream during the lifetime of its object. After some tinkering I came up with this:

#include <iostream>
#include <fstream>


class ScopedRedirect
{
public:
    ScopedRedirect(std::ostream & inOriginal, std::ostream & inRedirect) :
        mOriginal(inOriginal),
        mRedirect(inRedirect)
    {
        mOriginal.rdbuf(mRedirect.rdbuf(mOriginal.rdbuf()));
    }

    ~ScopedRedirect()
    {
        mOriginal.rdbuf(mRedirect.rdbuf(mOriginal.rdbuf()));
    }    

private:
    ScopedRedirect(const ScopedRedirect&);
    ScopedRedirect& operator=(const ScopedRedirect&);

    std::ostream & mOriginal;
    std::ostream & mRedirect;
};


int main()
{
    std::cout << "Before redirect." << std::endl;
    std::ofstream filestream("redirected.txt");
    {
        ScopedRedirect redirect(std::cout, filestream);
        std::cout << "During redirect." << std::endl;
    }
    std::cout << "After redirect." << std::endl;

    return 0;
}

It seems to work fine. However, it's weird that the following line is repeated in both the constructor and destructor:

mOriginal.rdbuf(mRedirect.rdbuf(mOriginal.rdbuf()));

I think it's correct, but I would like to verify with the SO community. Can you find any errors or dangers in this code?

Edit

Make non-copyable.

Upvotes: 23

Views: 8648

Answers (1)

GManNickG
GManNickG

Reputation: 503855

The reason those lines are the same is because what you're doing is swapping the buffers. (That is, you "redirect" by swapping the original buffer with the redirect buffer; restoration is the swap back.)

While this might give you the intended effect with respect to the output stream, it's not correct because the redirect stream now outputs somewhere else. To redirect means to take one stream and make it output somewhere else; note this doesn't effect that 'somewhere else'.

Your class is not a redirect; as is, it should really be named ScopedStreamSwap. For example, try this instead:

#include <iostream>
#include <fstream>

class ScopedRedirect
{
public:
    ScopedRedirect(std::ostream & inOriginal, std::ostream & inRedirect) :
        mOriginal(inOriginal),
        mRedirect(inRedirect)
    {
        mOriginal.rdbuf(mRedirect.rdbuf(mOriginal.rdbuf()));
    }

    ~ScopedRedirect()
    {
        mOriginal.rdbuf(mRedirect.rdbuf(mOriginal.rdbuf()));
    }    

private:
    ScopedRedirect(const ScopedRedirect&);
    ScopedRedirect& operator=(const ScopedRedirect&);

    std::ostream & mOriginal;
    std::ostream & mRedirect;
};


int main()
{
    std::cout << "Before redirect." << std::endl;
    std::ofstream filestream("redirected.txt");
    {
        ScopedRedirect redirect(std::cout, filestream);
        std::cout << "During redirect." << std::endl;

        // oops:
        filestream << "also to the file, right?...nope" << std::endl;
        filestream << "ah, why am i on the screen?!" << std::endl;
    }
    std::cout << "After redirect." << std::endl;

    // in main, return 0 is implicit, if there is no return statement;
    // helpful to keep in mind in snippets and short things
}

What you want is this:

#include <iostream>
#include <fstream>

class ScopedRedirect
{
public:
    ScopedRedirect(std::ostream & inOriginal, std::ostream & inRedirect) :
        mOriginal(inOriginal),
        mOldBuffer(inOriginal.rdbuf(inRedirect.rdbuf()))
    { }

    ~ScopedRedirect()
    {
        mOriginal.rdbuf(mOldBuffer);
    }    

private:
    ScopedRedirect(const ScopedRedirect&);
    ScopedRedirect& operator=(const ScopedRedirect&);

    std::ostream & mOriginal;
    std::streambuf * mOldBuffer;
};


int main()
{
    std::cout << "Before redirect." << std::endl;
    std::ofstream filestream("redirected.txt");
    {
        ScopedRedirect redirect(std::cout, filestream);
        std::cout << "During redirect." << std::endl;

        // yay:
        filestream << "also to the file, right?...yes" << std::endl;
        filestream << "i am not on the screen" << std::endl;
    }
    std::cout << "After redirect." << std::endl;

    return 0;
}

Upvotes: 22

Related Questions