ThomasNotTom
ThomasNotTom

Reputation: 51

Inheriting Structs in C++

I am working with the LibAV library, converting it from C to C++ (and moving to an object oriented style). This library has structs to hold data, which need to be created using specific allocation methods. For example:

AVPacket* packet = av_packet_alloc();
AVCodecContext* cdcContext = avcodec_alloc_context3(/* SOME PARAMETERS */)
AVFormatContext* fmtContext = avformat_alloc_context();

However I would like to add methods to each struct. For example, this:

AVFormatContext* fmtContext = avformat_alloc_context();
avformat_find_stream_info(fmtContext, NULL);

becomes

FormatContext* fmtContext = new FormatContext();
fmtContext->findStreamInfo(NULL);

Or in general given this struct

struct Foo {
    int a;
    int b;
    int c;
}

void printFoo(Foo* foo) {
    std::cout << foo->a << ", " << foo->b << ", " << foo->c << std::endl;
}

struct Foo foo = alloc_foo();

goes from

printFoo(foo)

to

foo->print();

In a solution I am looking to be able to use CodecContext as if it was a struct, while also being able to write custom methods onto it. I would also like the new classes to be substitutable into the methods in the pre-existing library.

Such that, for example, a method that takes in an AVCodecContext, can also take a CodecContext (the custom class)

I have tried various methods, however each come with caveats and reduced memory safety. One attempt was using memcpy and struct inheritance to ensure the correct allocation methods are ran.

class CodecContext : public AVCodecContext {
public:
    CodecContext(const AVCodec* codec);
};

CodecContext::CodecContext(const AVCodec* codec) {
    AVCodecContext* tempCtx = avcodec_alloc_context3(codec);
    if (!tempCtx) {
        std::cerr << "ERROR: AVCodecContext could not be allocated" << std::endl;
        return;
    }
    std::memcpy(static_cast<AVCodecContext*>(tempCtx), tempCtx, sizeof(AVCodecContext));
    avcodec_free_context(&tempCtx);
}

However this does not seem to work as intended, causing memory issues when the CodecContext is used in conjunction with other methods.

Upvotes: 1

Views: 92

Answers (3)

ThomasNotTom
ThomasNotTom

Reputation: 51

My current solution relies on inheriting the AVCodecContext struct, allocating the struct and then copying the data using typecasting. The delete keyword is overwritten to prevent double de-allocation. I have done some testing, and am yet to find any issues with this solution, and for the moment, will be using this in my project.

OOAVCodecContext.hpp

struct OOAVCodecContext : AVCodecContext {
public:
    OOAVCodecContext(const AVCodec* codec);
    ~OOAVCodecContext();
    void operator delete(void* ptr) {};

    // Add in custom data

    int a = 100;
    int b = 1000;
    int c = 10000;
    int d = 100000;
    const char* str = "Hello, world!";
};

OOAVCodecContext.cpp

OOAVCodecContext::OOAVCodecContext(const AVCodec* codec) {
    AVCodecContext* tempCtx = avcodec_alloc_context3(codec);
    *(AVCodecContext*)(this) = *tempCtx;
}

OOAVCodecContext::~OOAVCodecContext() {
    AVCodecContext* tempCtx = (AVCodecContext*)(this);
    avcodec_free_context(&tempCtx);
}

main.cpp

int main() {
    OOAVCodecContext* codecContext = new OOAVCodecContext(avcodec_find_encoder(AV_CODEC_ID_H264));
    
    std::cout << codecContext->bit_rate << std::endl;
    std::cout << codecContext->channels << std::endl;
    std::cout << (codecContext->codec_id == AV_CODEC_ID_H264) << std::endl;

    std::cout << codecContext->a << std::endl;
    std::cout << codecContext->b << std::endl;
    std::cout << codecContext->c << std::endl;
    std::cout << codecContext->d << std::endl;
    std::cout << codecContext->str << std::endl;

    delete codecContext;
}

Upvotes: 0

irrenhaus3
irrenhaus3

Reputation: 309

I would recommend an architecture based on the RAII principle, like so:

class Wrapper
{

  static const auto MyDeleter = [](TheWrappedType* p)
  {
    the_c_destructor_fn(p);
  };

  std::unique_ptr<TheWrappedType, MyDeleter> m_theThing;

public:

  Wrapper(SomeDependency const& dep):
    m_theThing(the_c_construction_fn(&dep))
  {}


  TheWrappedType *const get()
  {
    return m_theThing.get();
  }

  void someOperation()
  {
    the_c_operation(m_theThing.get());
  }

  // etc.

};

And then using that wrapper type something like this:

int main()
{
  SomeDependency dep = PrepareTheInput();
  Wrapper theThing(dep);

  theThing.someOperation();
  // etc.
}

That way, you can use the C library behind an interface made out of classes which

  • are composable into other classes
  • can be used as value-types and passed around much more safely than pointers, since they can never be null or invalid
  • and allow acces to their owned memory via a get() function, in case you need to pass it to a C API function after all.

Caveat:
This approach brings some difficulties, because the Wrapper class cannot be copied due to its unique_ptr member; that's because the semantics of copying an object that owns dynamic memory are not obvious.
You still get move semantics, which means you can pass ownership of Wrapper objects around your code (i.e. you can still safely store them in a std::vector), but as soon as you actually need to make a copy of something, you'll run into difficulties.
Still, this is an approach to consider, since it's the most natural way of grouping a library object and the operations that manipulate it together.
Hope you find this helpful, good luck!

Upvotes: 1

Shai List
Shai List

Reputation: 31

Just by looking at the documentation, we can assume nothing about how and where the AVCodecContext is actually initialized and placed.
The avcodec_alloc_context3 function returns a pointer to the struct, which you should conceptually treat as "I have no control over the creation of this object".

Since you don't have control over the creation of the object, you can't really inherit from its type, since the allocating function doesn't create your type, it just creates the base type.

What you could do is to wrap the object in a managing class.

You could also use smart pointers to automatically manage its lifetime:

#include <memory>

struct CodecContextDeleter {
    void operator()(AVCodecContext* context) {
        avcodec_free_context(&context);
    }
};

using CodecContext = std::unique_ptr<AVCodecContext, CodecContextDeleter>;

Upvotes: 3

Related Questions