Search code examples
c++delegatesostream

Adding a new line after std::ostream output without explicitly calling it


Example below illustrates my question best:

class Stream
{
public:
    Stream(std::ostream& os)
        :
        stream(os)
    {
    }

    auto getStream()
    {
        return std::pair<std::ostream&, std::unique_ptr<StreamDelegate>>(stream, std::make_unique<StreamDelegate>(stream));
    }

private:
    std::ostream& stream;
};  

int main()
{
    Stream os(std::cout);
    os.getStream() << "input1" << "input2"; 
//execute some code without explicitly calling it 
//such os.getStream() << std::endl; 
}

My (working) attempt achieving this functionality:

class StreamDelegate
{
public:
    StreamDelegate(std::ostream& os)
        :
        stream(os)
    {
    }
    ~StreamDelegate()
    {
        //some delegated functionality
        //exmpl:
        stream << std::endl;
    }

private:
    std::ostream& stream;
};

class Stream
{
public:
    Stream(std::ostream& os)
        :
        stream(os)
    {
    }

    auto getStream()
    {
        return std::pair<std::ostream&, std::unique_ptr<StreamDelegate>>(stream, std::make_unique<StreamDelegate>(stream));
    }

private:
    std::ostream& stream;
};

int main()
{
    Stream os(std::cout);
    os.getStream().first << "input1" << " input2";
    os.getStream().first << "line2: input1" << " line2: input 2";

    std::cin.get();
}

Are there other, prefereably more elegant, ways to achieve this functionality?

Also, i see a possible pitfall in my code. Ilustrated in the code below:

int main()
{
    Stream os(std::cout);
    auto pitfall = os.getStream();
    pitfall.first << "line1";
    pitfall.first << "should be line 2";

    std::cin.get();
}

Since the return value of os.getStream() is assigned to a variable, there is no destruction of the StreamDelegate and thus no desired behaviour.

I dont intend for Stream::getStream() to be assigned to a variable, but it if there is a way around this i'd like to know.

Also I am aware that I could have avoided having StreamDelegate by implementing the Stream destructor in the same manner as StreanDelegate, but i possibly want this to work on a singleton.


Solution

  • I think your idea to use RAII is good. If you implement templated operator<< for your Stream and have that return your StreamDelegate it get easier to use, and it's harder to mess up the destruction by assigning to a variable.

    #include <iostream>
    
    class StreamDelegate
    {
    public:
        StreamDelegate(std::ostream& os) : stream(os) {}
    
        ~StreamDelegate() {
            //some delegated functionality
            //exmpl:
            stream << std::endl;
        }
    
        template <typename T>
        StreamDelegate& operator<<(T&& val) {
            stream << std::forward<T>(val);
            return *this;
        }
        StreamDelegate& operator=(const StreamDelegate&) = delete;
        StreamDelegate(const StreamDelegate&) = delete;
    
    private:
        std::ostream& stream;
    };
    
    class Stream
    {
    public:
        Stream(std::ostream& os) : stream(os) {}
    
        template <typename T>
        StreamDelegate operator<<(T&& val) {
            stream << std::forward<T>(val);
            return StreamDelegate(stream);
        }
    
    private:
        std::ostream& stream;
    };
    
    int main()
    {
        Stream os(std::cout);
        os << "input1" << " input2";
        os << "line2: input1" << " line2: input 2";
    
        std::cin.get();
    }
    

    Edit

    I explicitly declared both copy-constructor and copy-assignment for StreamDelegate as delete, so it's no longer possible to do

    auto delegate = os << "input3";