How to use smart pointer for automatic cleaning?

I am making a simple logging class with a pointer to std::ofstream or std::cerr .

Is there an easy way to use the smart pointer to automatically clean up no matter which stream is used?

The code should compile on clang ++, g ++ and VS2013.

the code

 #include <iostream> #include <fstream> #include <string> class Logger { private: std::ostream * output_stream{ nullptr }; bool using_file{ false }; public: Logger() { output_stream = &std::cerr; using_file = false; } Logger(std::string file) { output_stream = new std::ofstream(file); using_file = true; } ~Logger() { if (using_file) { delete output_stream; } } template<typename T> void log(T info) { *output_stream << info << std::endl; } }; class tmp { int i{ 4 }; friend std::ostream & operator<<(std::ostream &os, const tmp& p); }; std::ostream &operator<<(std::ostream &os, const tmp& p) { return os << pi; } int main() { tmp t; Logger logger; logger.log(t); system("pause"); return 0; } 

Attempts

std::unique_ptr

I can use std::unique_ptr for the file as follows:

 std::unique_ptr<std::ostream> p; p = std::make_unique<std::ofstream>("file.txt"); *p << "hi there" << std::endl; 

Trying this with std::cout warns me of a remote function (assuming a constructor.

 std::unique_ptr<std::ostream> p2; p2 = std::make_unique<std::ostream>(std::cout); *p2 << "hey" << std::endl; 

std::shared_ptr

Because std::unique_ptr is only for owning things, and std::cout should not belong, I thought I would try std::shared_ptr

 std::shared_ptr<std::ostream> p; p = std::make_shared<std::ostream>(std::cout); *p << "hola" << std::endl; 

It gives me the same error with remote constructor. p = &std::cout complains about type mismatch, so it doesn't work either.

+4
source share
4 answers

You can use shared_ptr with a delete that does not delete anything in the case of cerr and just a normally-built shared_ptr in the case ofstream

 class Logger { private: std::shared_ptr<std::ostream> output_stream{ nullptr }; public: Logger() : output_stream(&std::cerr, [](std::ostream*){}) { } Logger(std::string file) : output_stream(std::make_shared<std::ofstream>(file)) { } // default destructor is OK template<typename T> void log(T info) { *output_stream << info << std::endl; } }; 
+4
source

I would just have two pointers, one smart and one raw.

A raw pointer is always used to refer to a stream. If necessary, a smart pointer is used for cleaning.

 class Logger { private: std::unique_ptr<std::ofstream> file_stream; std:ostream *stream; public: Logger() : stream(&std::cerr) {} Logger(const std::string& file) : file_stream(std::make_unique<std::ofstream>(file)), stream(file_stream.get()){} template<typename T> void log(T info) { *stream << info << std::endl; } }; 
+4
source

I try to avoid cases where I want the object to "own" such things. At a time when I didnโ€™t have much choice, I ended up working with the โ€œshouldDeleteโ€ flag or callback.

 class Logger { public: Logger(std::ofstream *outputStream, bool deleteOutputStream) : outputStream(outputStream), deleteOutputStream(deleteOutputStream) { } ~Logger() { if (deleteOutputStream) delete outputStream; } }; Logger logger(&std::cout, false); class Logger { public: typedef std::function<void(std::ostream*)> Deleter; typedef std::unique_ptr<std::ostream, Deleter> OStreamPointer; Logger(OStreamPointer &&outputStream) : outputStream(std::move(outputStream)) { } ~Logger() { } private: OStreamPointer outputStream; }; Logger logger(Logger::OStreamPointer( &std::cout, [](std::ostream*) {})); //no-op callback 
+1
source

You can do this by issuing a smart pointer in the destructor (and elsewhere) in cases where it should not be deleted, but this is not worth the trouble with IMO.

Instead, I would recommend just using two pointers: one for threads that need to be managed, and one for those that don't:

 class Logger { private: std::ostream * unmanaged_stream{ nullptr }; std::unique_ptr<std::ostream> managed_stream{ nullptr }; bool using_file{ false }; std::ostream& output_stream() { return using_file ? *managed_stream : *unmanaged_stream; } public: Logger() : unmanaged_stream{&std::cerr}, using_file{false} { } Logger(const std::string& file) : managed_stream{std::make_unique<std::ofstream>(file)}, using_file{true} { } template<typename T> void log(T info) { output_stream() << info << std::endl; } }; 

If space preservation is a priority, you can put them in a union, but then you have to explicitly name the destructor and a new place to define the active member, which again is more complicated and probably not worth it.

+1
source

All Articles