现代C和C :可以将一个定义的结构用于其他声明的结构

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

本文关键字:结构 一个 定义 用于 声明 其他 现代      更新时间:2023-10-16

假设我想制作某种引擎,以支持加载图形Image s,所以我有

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

我不希望外部世界知道Image的确是什么,他们只会处理指针。

但是,在engine内,我想使用特定类型,例如SDL_Surface在SDL中完全定义。

我可以以某种方式重新重新用于此文件的图像,因此编译器每次看到Image*(除宏除外)时都假定SDL_Surface*

即。我想要typedef struct SDL_Surface Image;

之类的东西

所有尝试都像

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

产生编译时间错误(http://codepad.org/1cfn18oh)。

我知道我可以在engine.c/engine.cpp中使用struct Image{SDL_Surface* surface};,但它会创建不必要的间接方式,我必须键入->surface。另一个肮脏的解决方案是使用明确的铸件,例如((SDL_Surface*)image),但我在重命名的清洁剂中很有趣。

ps。我对C和C 的答案感兴趣。

简单地定义一个别名:

using Image = SDL_Surface;
typedef SDL_Surface Image;

汇编正好。

如果您需要隐藏SDL_Surface,只需将其导入某些匿名或detail名称命名空间并将其类似地导入。


如果出于某些原因,您要定义自己的Image类型,则可以随时声明A(n)(隐式)转换功能/操作员,例如:

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

,也可以返回Image,如果您需要:

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

在C 中您可以使用继承:

// 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 !! :-)

备注:使用类和私人继承会更安全,因此只有图像知道它是一个sdl_surface。

在某些情况下,从现有的实现类中继承可能是不希望的(例如,如果您需要虚拟驱动器,而基类无需)。然后,pimpl习语可以是替代方案(以额外的间接为代价):

//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;
}

PIMPL的主要优点是,您不仅可以公开不完整的类型,因此可以向客户提供一些有用的成员功能。但是,如果您不需要这个,并且由于您已经与POITNER一起使用了客户端的对象,那么您也可以直接转到组成并拥有ImageImpl成员而不是PIMPL指针。

在C 中,您不能使用继承。但是构图肯定会解决问题:

struct Image {
   SDL_Surface s; 
   }; 

通常使用PIMPL(指向实现)模式进行此类操作。但是,如果您想暂时避免间接,或者类型不完整(SDL_Surface不是这种情况,但是与许多其他SDL类相关),则可以使用void的指针,因为它可以指向任何数据,然后将其施放在实施方面。

在这里,我们使用std::unique_ptr使用零规则。这种Image现在是不可复制的,但可移动。如果您想能够复制它,请使用 value_ptr -like指针(不在标准中,但可以轻松地写下这样的指针或使用第三方)

#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());
}

如果您的客户端代码对图像没有任何作用,除了传递指针,您可以使用Windows API技巧:

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

在C 中您可以求助于不完整的类型。

因此,您在标题文件中定义API:

myapi.h

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

请注意,您的struct Image类型虽然适用于您的客户,但已完全隐藏。

现在您的实施完成了您的结构的完整声明:

myapi.c:

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

您将编译的C代码(对象,静态或动态库)和客户的标题捆绑在一起。

在您导出的标题中,您可以转发SDL_Surface
然后将Image声明为指针。喜欢:

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

这样,可以在没有SDL标题的情况下使用库。
但是,仍然需要sdl.dll。