c++方法的配置没有宏

C++ approach to configuration without macro

本文关键字:配置 方法 c++      更新时间:2023-10-16

我正在实现一个库,并使用抽象类/接口,其中有一些不同的实现。我想让用户选择特定的实现使用配置文件,如config.h。我有两个问题:

    如何在没有宏的情况下实现这一点?
  • 我使用继承来实现子类,但我不想在运行时动态选择类,我们仍然可以实现这一点吗?

我正在寻找c++11的解决方案,但c++14和c++17也是受欢迎的。

例如:

#include "config.h"
class library1_base {
  virtual void blah() = 0;
  virtual void bleh() = 0;
}
#ifdef LIB1_USE_IMPL1
class impl1_lib1 : library1_base { ... };
using library1 = impl1_lib;
#else
class impl2_lib1 : library1_base { ... };
using library1 = impl2_lib;
#endif
class library2_base {
  virtual void ah() = 0;
  virtual void oh() = 0;
}
#ifdef LIB2_USE_IMPL1
class impl1_lib2 : library2_base { ... };
using library2 = impl1_lib2;
#else
class impl2_lib2 : library2_base { ... };
using library2 = impl2_lib2;
#endif

config.h如下(使用宏)

#define LIB1_USE_IMPL1
#define LIB2_USE_IMPL2

如何使用:

library1 x; // now this library1 should be using impl1_lib1
x.blah();
x.bleh();
library2 y; // now this library2 should be using impl2_lib2
y.ah();
y.oh();

首先,问自己"我该如何选择config,为什么?"

宏无处不在(您可以将它们定义为编译器标志,也可以在代码中定义),但它们缺乏作用域功能(它们是全局的,例如,不能定义为名称空间局部的),因此它们容易发生名称冲突。

如果你关心的是后者,你可以用typedefs/constants定义一个特殊的配置类,如下所示:

namespace myapp {
   struct Config {
      class library : library_base { ... }
      using other_library = some_other_library;
      using version_t = int;
      static const version_t VERSION = 1;
   }
}

但是要选择所需的配置,您必须包含多个configXXX.h文件中的一个,或者创建配置类模板并为每个配置设置多个专门化,如下所示:

enum class ConfigType {
   JPEG, PNG
};
template <ConfigType type> struct Configs;
template <> struct Configs<ConfigType::JPEG> {
   using library = libjpeg;
};
template <> struct Configs<ConfigType::PNG> {
   using library = libpng;
};
...
static const ConfigType CONFIG_TYPE = ConfigType::JPEG;
...
using Config = Configs<CONFIG_TYPE>;
using library = Config::library;

更新#1:在后面的例子中,组合似乎是最好的解决方案(我已经重命名了东西,所以他们更有意义):

// configs.hpp
enum class AudioType {
   MP3, OGG
};
template <AudioType type> struct AudioConfigs;
template <> struct AudioConfigs<AudioType::MP3> {
   using Library = libmp3; // of course, you can define class in-place instead of typedef
};
template <> struct AudioConfigs<AudioType::OGG> {
   using Library = libvorbis;
};
enum class ImageType {
   JPEG, PNG
};
template <ImageType type> struct ImageConfigs;
template <> struct ImageConfigs<ImageType::JPEG> {
   using Library = libjpeg;
};
template <> struct ImageConfigs<ImageType::PNG> {
   using Library = pnglib;
};
template <AudioType audiotype, ImageType imagetype> struct Configs {
   using AudioLibrary = typename AudioConfigs<audiotype>::Library;
   using ImageLibrary = typename ImageConfigs<imagetype>::Library;
};
// config_setup.hpp - this one you will edit
#include "configs.hpp"
using Config = Configs<AudioType::MP3, ImageType::JPEG>;
// config.hpp
#include "config_setup.hpp"
using AudioLibrary = typename Config::AudioLibrary;
using ImageLibrary = typename Config::ImageLibrary;
// real code
#include "config.hpp"
AudioLibrary audioLibrary;
audioLibrary.doStuff();
替代结构:

template <AudioType audiotype, ImageType imagetype> struct Configs {
   using Audio = typename AudioConfigs<audiotype>;
   using Image = typename ImageConfigs<imagetype>;
};
// Options should be accessed like Config::Audio::Library now

更新#2:(只是为了说明无尽的可能性)使用一些可变模板魔法,您可以缩短AudioConfigs/ImageConfigs/等。:

template <AudioConfig config> struct AudioConfigs : public LibraryConfigs<AudioConfig, config, AudioType::MP3, AudioType::OGG, libmp3, libvorbis> { };

…完全避免了带有多个专门化的样板代码。

你将失去在原地定义类的可能性,这就是你在你的例子中所做的,所以交叉。