Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assign derived class unique_ptr to base class unique_ptr

I created a custom istream derived from std::istream that uses a custom streambuf when the file is a zipped file and std::filebuf otherwise.

#mystream.h
class my_stream: public istream {
  public:
     explicit my_stream(const std::string &path);
  private:       
     std::unique_ptr<std::streambuf> b_;
}

#mystream.cpp
my_stream::my_stream(const std::string &path) :std::istream(nullptr) {
    if(path.substr(path.length()-6, path.length())==".gzip"){
        b_ = std::make_unique<gzipbuf>(path); //gzipbuf is derived from std::streambuf
    } 
    else {
        std::unique_ptr<std::filebuf> fb;
        fb->open(path.c_str(), std::ios::in);
        b_ = fb;
    }
    this->init(b_.get());
}

I am able to assign derived class unique_ptr to base class unique_ptr at one place

b_ = std::make_unique<gzipbuf>(path);

but not at the other

b_ = fb;

It says

candidate function not viable: no known conversion from 'unique_ptr<std::filebuf, default_delete<std::basic_filebuf<char>>>' to 'unique_ptr<std::basic_streambuf<char>, default_delete<std::basic_streambuf<char>>>' for 1st argument
      operator=(unique_ptr&& __u) noexcept
like image 512
pikachu Avatar asked Mar 11 '23 23:03

pikachu


2 Answers

Firstly, after this line

std::unique_ptr<std::filebuf> fb;

fb doesn't actually point at anything, it is just an empty unique_ptr so you are invoking undefined behaviour here:

fb->open(path.c_str(), std::ios::in);

To fix this just change the line to:

auto fb = std::make_unique<std::filebuf>();

Regarding the error you are getting, if this line were allowed

b_ = fb;

then afterwards both b_ and fb would point at the same object. This is not allowed by unique_ptr. A resource can be owned by one, and only one, unique_ptr. One solution is to pass ownership from fb to b_ using std::move:

b_ = std::move(fb)

and then fb no longer owns anything.

Personally, I like to initialize member variables in the constructor initializer list wherever possible and would extract out the creation of the streambuf to a separate function in order to do so:

std::unique_ptr<std::streambuf> createStream(const std::string &path) {
    if(path.substr(path.length()-5, path.length())==".gzip"){  // I think you meant 5 here!
        return std::make_unique<gzipbuf>(path); 
    }
    auto fb = std::make_unique<std::filebuf>();
    fb->open(path.c_str(), std::ios::in);
    return fb;
}

Then the constructor of my_stream can be:

my_stream::my_stream(const std::string &path) : std::istream(nullptr),
  b_(createStream(path)) {
    this->init(b_.get());
}
like image 172
Chris Drew Avatar answered Mar 19 '23 11:03

Chris Drew


This happened to me, but it was because I forgot to inherit publicly :)

// Was:
class Derived: Base
{
};

// Should have been:
class Derived: public Base
{
};
like image 25
citelao Avatar answered Mar 19 '23 11:03

citelao