6

Let's say I want to make some sort of engine which should support loading graphical Images, 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++.

Christophe
  • 54,708
  • 5
  • 52
  • 107
  • As far as I know, `struct Image{SDL_Surface surface};` (no pointer-to) is the closest available approximation, but I'll be interested to find out if there's something better now. – zwol May 10 '15 at 14:37
  • Oh, in C++ did you try `struct Image : public SDL_Surface {};` ? That has a bunch of rough edges but it might just do what you want. – zwol May 10 '15 at 14:37
  • @zwol this work, but I'll still have to use casts to convert `SDL_Surface*` which are returned by SDL functions. Using (no pointer-to) is not possible because SDL returns pointers. – user2998754 May 10 '15 at 14:44
  • If your clients need to use the SDL api anyway, why even bother definining `Image`? – rwols May 10 '15 at 15:36
  • Can you use incomplete types? – n. 'pronouns' m. May 10 '15 at 16:09
  • You have to decide if your `SDL_Surface` is an implementation detail, or it isn't. You seem to want both, which isn't possible. – milleniumbug May 10 '15 at 16:26
  • I just thought of a hilarious trick - you could set up something like a conditional-compilation "guard" to block the `struct Image;` line for appearing in the file where you use `SDL_Surface`. That needs a little bit of macros, though... But it'd work fine in both C and C++! – Mints97 May 10 '15 at 18:52
  • can I assume, that you are creating a library, and that you don't want the users of that library to need to have SDL headers? – sp2danny Jun 05 '15 at 04:21

6 Answers6

3

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&);
    // ...
};
Shoe
  • 70,092
  • 30
  • 150
  • 251
  • That's because in your example SDL_Surface is defined. The point is I don't want world to know that SDL_Surface exists. I want existence of SDL_Surface to be known only in file that implements image loading. It's internal detail and I don't want to include bunch of unnecessary headers. – user2998754 May 10 '15 at 14:49
  • @user2998754 Just import `SDL_Surface` inside an anonymous (or `detail` named, even better) namespace, and use it [like this](http://coliru.stacked-crooked.com/a/f4f0701a6d92132f). – Shoe May 10 '15 at 14:58
1

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; 
   }; 
Community
  • 1
  • 1
Christophe
  • 54,708
  • 5
  • 52
  • 107
1

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());
}
milleniumbug
  • 14,714
  • 3
  • 43
  • 70
0

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(...);
zmbq
  • 35,452
  • 13
  • 80
  • 153
  • That's not what the Windows API does (not when `STRICT` is defined, anyway, and you should *always* define `STRICT`) – Ben Voigt May 10 '15 at 15:28
  • Haven't programmed any Windows API code in the past decade or so, so I wasn't aware of STRICT. I looked it up in `winnt.h` - cool! – zmbq May 10 '15 at 18:21
0

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.

rslemos
  • 2,108
  • 17
  • 28
0

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.

sp2danny
  • 6,824
  • 2
  • 27
  • 49