Modern C and C ++: can I use one specific structure for another declared structure?

Let's say I want to make some kind of engine that should support loading Image s, so I have

  struct Image; Image* load_image_from_file(...); 

I do not want the outside world to know that Image in fact, they will only deal with pointers to it.

However, inside the engine I want to use a specific type, for example. SDL_Surface , which is fully defined in the SDL.

Is there any way to change the image for this file, so the compiler accepts SDL_Surface* every time it sees Image* (except for the macro)?

those. I want something like typedef struct SDL_Surface Image;

All attempts like

  using Image = SDL_Surface; typedef SDL_Surface Image; typedef struct SDL_Surface Image; 

produce a compile-time error ( http://codepad.org/1cFn18oh ).

I know that I can use something like struct Image{SDL_Surface* surface}; in engine.c / engine.cpp , but it creates unnecessary indirection, and I will need to type ->surface . Another dirty solution is to use explicit tricks, for example ((SDL_Surface*)image) , but I'm interested in a cleaner renaming.

PS. I'm interested in the answers for both C and C ++.

+6
source share
6 answers

Just define an alias:

 using Image = SDL_Surface; typedef SDL_Surface Image; 

which compiles just fine.

If you need to hide SDL_Surface , just import it into some kind of anonymous or detail namespace and use it like this .


If for some reason you want to define your own type of Image , you can always declare a (n) (implicit) function / conversion operator, for example:

 struct Image { /* explicit */ operator SDL_Surface() const; // ... }; 

and also return to Image if you need it:

 struct Image { /* explicit */ Image(SDL_Surface&&); /* explicit */ Image(SDL_Surface const&); // ... }; 
+3
source

In C ++ you can use inheritance:

 // User view struct Image; // forward declaration (inclomplete type). Image* LoadFromFile (...); // You can use pointer to incomplete type // Implementation view struct Image: SDL_Surface { }; // here you go !! :-) 

Note: it would be safer to use classes and personal inheritance, so that only the image knows that it is SDL_Surface.

In some cases, it would be undesirable to inherit from an existing implementation class (for example, if you need a virtual destructor and the base class is not). Then PIMPL idiom can be an alternative (at the cost of additional indirectness):

 //User View unchanged struct Image; int TestImage(Image*z); //implementation view struct Image { struct ImageImpl { int x; }; // nested definition or typedef or whatever ImageImpl *p; // works in every case, at cost of an extra indirection instead of a pointer }; int TestImage(Image* z) { return z->p->x; } 

The main advantage of PIMPL here is that you can expose more than just an incomplete type, and therefore offer your clients some useful member functions. But if you do not need it, and when you are already working with poitners on an object from the client side, you can also go directly to the composition and have an ImageImpl element instead of a PIMPL pointer.

In C, you cannot use inheritance. But the composition would certainly do the trick:

 struct Image { SDL_Surface s; }; 
+1
source

Such operations are usually performed using the PIMPL template (implementation pointer). But if you want to avoid indications at the moment or if the type is incomplete (this does not apply to SDL_Surface , but it applies to many other SDL classes), you can use a pointer to void , since it can point to any data, and then apply it to implementation side.

Here we use std::unique_ptr to use Rule of Zero . Such an Image now not copied, but movable. If you want to copy it, use the value_ptr -like pointer (not in the standard, but you can easily write one yourself or use a third-party one)

 #include <memory> struct ImageDeleter { void operator()(void* ptr) const; }; class Image { public: // but don't touch it std::unique_ptr<void, ImageDeleter> internal; private: /* private operations on surface */ public: /* public operations */ void save(const std::string& path) const; Image(int width, int height); }; // EXAMPLE USAGE // Image img(640, 480); // img.save("aaa.bmp"); // IN THE DEFINITION FILE #include <SDL2/SDL.h> namespace detail { SDL_Surface* as_surface(const Image& img) { return static_cast<SDL_Surface*>(img.internal.get()); } } void ImageDeleter::operator()(void* ptr) const { SDL_FreeSurface(static_cast<SDL_Surface*>(ptr)); } Image::Image(int width, int height) : internal(SDL_CreateRGBSurface(0, width, height, 32, 0, 0, 0, 0)) { } void Image::save(const std::string& path) const { SDL_SaveBMP(detail::as_surface(*this), path.c_str()); } 
+1
source

If your client code does nothing with the image other than passing a pointer to it, you can use the Windows API trick:

 typedef void *HIMAGE; // Image Handle HIMAGE LoadImage(...); 
0
source

In C, you can resort to an incomplete type.

So you define your API in the header file:

myapi.h

 struct Image; struct Image* load_image_from_file(...); ... 

Please note that your struct Image type, although available to your clients, is completely hidden from them.

Now your implementation makes a complete declaration of your structure:

myapi.c:

 struct Image { /* whatever you want to put here */ /* even SDL_Surface */ /* or pointer to it */ }; /* function bodies */ 

You collect compiled C code (object, static or dynamic library) and a header for your clients.

0
source

In the header you export, you can forward SDL_Surface
and then declare Image pointer to it. How:

 struct SDL_Surface; typedef SDL_Surface* Image; extern Image load_image_from_file(char*); 

Thus, your library can be used without SDL headers.
However, SDL.dll is still needed.

0
source

Source: https://habr.com/ru/post/986928/


All Articles