Reputation: 51
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
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
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
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
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