Reputation: 89
Let's say I want to make some sort of engine which should support loading graphical Image
s, so I have
struct Image;
Image* load_image_from_file(...);
I don't want external world to know what Image
really is, they'll deal only with pointers to it.
However inside engine
I want to use specific type, e.g. SDL_Surface
which is fully defined in SDL.
Can I somehow redifine Image for this file so compiler assumes SDL_Surface*
each time it sees Image*
(other than macro)?
I.e. 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 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'll have to type ->surface
.
Another dirty solution is to use explicit casts, e.g.((SDL_Surface*)image)
but I'm interesting in cleaner renaming.
PS. I'm interested in answers for both C and C++.
Upvotes: 6
Views: 269
Reputation: 15814
Such operations are normally done with PIMPL (pointer to implementation) pattern. But if you want to avoid indirections for now, or if the type is incomplete (this isn't the case with SDL_Surface
, but it is with many other SDL classes) you can use pointer to void
, since it can point to any data, and then cast it on the implementation side.
Here, we use std::unique_ptr
to make use of Rule of Zero. Such Image
is now non-copyable, but movable. If you want to be able to copy it, use a value_ptr
-like pointer (not in the standard, but you can easily write such pointer 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());
}
Upvotes: 1
Reputation: 7644
In the header you export you can forward SDL_Surface
and then declare Image
to be a pointer to it. Like:
struct SDL_Surface;
typedef SDL_Surface* Image;
extern Image load_image_from_file(char*);
This way your library can be used without the SDL headers.
However, SDL.dll is still needed.
Upvotes: 0
Reputation: 2731
In C you can resort to an incomplete type.
So you define your API in a header file:
myapi.h
struct Image;
struct Image* load_image_from_file(...);
...
Note that your struct Image
type, though available for your clients, is completely hidden from them.
Now your implementation does the full declaration of your struct:
myapi.c:
struct Image {
/* whatever you want to put here */
/* even SDL_Surface */
/* or pointer to it */
};
/* function bodies */
You bundle the compiled C code (object, static or dynamic library) and the header to your clients.
Upvotes: 0
Reputation: 73376
In C++ you can use the 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 !! :-)
Remark: it would be safer to use classes and private inheritance, so that only Image knows that it is an SDL_Surface.
In some cases it could be undesirable to to inherit from an existing implementation class (for example if you'd need a virtual destructor and the base class doesn't). Then the PIMPL idiom could be an alternative (at the cost of an additional indirection):
//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 could expose more than just an incomplete type, and hence offer to the clients some useful member functions. But if you don't need this, and as you already work with poitners to the object on the client side, you could as well go directly to composition and have an ImageImpl
member instead of a PIMPL pointer.
In C, you can't use inheritance. But composition would certainly do the trick:
struct Image {
SDL_Surface s;
};
Upvotes: 1
Reputation: 39013
If your client code doesn't do anything with the image, other than pass a pointer to it, you can use the Windows API trick:
typedef void *HIMAGE; // Image Handle
HIMAGE LoadImage(...);
Upvotes: 0
Reputation: 76240
Simply 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 anonymous or detail
-named namespace and use it like this.
If, for some reasons, you want to define your own Image
type, you can always declare a(n) (implicit) conversion function/operator, like:
struct Image {
/* explicit */ operator SDL_Surface() const;
// ...
};
and also back to Image
, if you need that:
struct Image {
/* explicit */ Image(SDL_Surface&&);
/* explicit */ Image(SDL_Surface const&);
// ...
};
Upvotes: 3