Ben
Ben

Reputation: 6348

How to use smart pointer for auto clean-up?

I'm making a simple logging class with a pointer to either a std::ofstream or std::cerr.

Is there any simple way to use a smart pointer for auto clean-up regardless of which stream is used?

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

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 << p.i;
}

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 like so:

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 about a deleted function (assuming that's the 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 shouldn't be owned, I thought I'd 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 deleted constructor error. p = &std::cout complains about a type mismatch, so it's also not working.

Upvotes: 5

Views: 2276

Answers (4)

Chris Drew
Chris Drew

Reputation: 15334

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

The raw pointer is always used to refer to the stream. The smart pointer is just used for clean-up if needed.

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

Upvotes: 5

flogram_dev
flogram_dev

Reputation: 42858

You could do this by releasing the smart pointer in the destructor (and elsewhere) in the cases where it shouldn't be deleted, but that's not worth the hassle IMO.

Instead, I'd recommend simply using two pointers: one for streams 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 saving space is a priority you could put them in a union, but then you'd have to explicitly call the destructor and placement new to define the active member, which again is more hassle and probably not worth it.

Upvotes: 1

ex-bart
ex-bart

Reputation: 1392

You can use a shared_ptr with a deleter that does not delete anything in the case of cerr and just a normally constructed shared_ptr in the case of 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;
    }
};

Upvotes: 5

Fire Lancer
Fire Lancer

Reputation: 30145

I tend to try to avoid cases where I want an object to "own" such things. In the times I did not have much choice, I ended up settling with a "shouldDelete" flag or a 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

Upvotes: 1

Related Questions