user2998754
user2998754

Reputation: 89

Modern C and C++: it is possible to use one defined structure for other declared structure?

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++.

Upvotes: 6

Views: 269

Answers (6)

milleniumbug
milleniumbug

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

sp2danny
sp2danny

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

rslemos
rslemos

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

Christophe
Christophe

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

zmbq
zmbq

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

Shoe
Shoe

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

Related Questions