C++ 尽可能灵活地使用泛型数据类型的指针、引用和句柄

C++ 尽可能灵活地使用泛型数据类型的指针、引用和句柄,c++,architecture,generic-programming,C++,Architecture,Generic Programming,在我的应用程序中,我有很多不同的数据类型,例如汽车、自行车、人。。。(它们实际上是其他数据类型,但这只是示例) 因为我的应用程序中也有相当多的“通用”代码,而且应用程序最初是用C编写的,指向汽车、自行车、人的指针。。。通常作为无效指针传递给这些通用模块,并带有类型标识,如下所示: Car myCar; ShowNiceDialog ((void *)&myCar, DATATYPE_CAR); “ShowNiceDialog”方法现在使用元信息(将数据类型映射到接口以从车中获取实际数据

在我的应用程序中,我有很多不同的数据类型,例如汽车、自行车、人。。。(它们实际上是其他数据类型,但这只是示例)

因为我的应用程序中也有相当多的“通用”代码,而且应用程序最初是用C编写的,指向汽车、自行车、人的指针。。。通常作为无效指针传递给这些通用模块,并带有类型标识,如下所示:

Car myCar;
ShowNiceDialog ((void *)&myCar, DATATYPE_CAR);
“ShowNiceDialog”方法现在使用元信息(将数据类型映射到接口以从车中获取实际数据的函数)根据给定的数据类型获取车的信息。这样,泛型逻辑只需编写一次,而不是针对每种新的数据类型每次都编写一次

当然,在C++中,使用一个普通的根类,比如

,可以使这一点变得简单多了。
class RootClass
   {
   public:
      string getName() const = 0;
   };

class Car : public RootClass
   {
   ...
   };

void ShowNiceDialog (RootClass *root);
问题是,在某些情况下,我们不希望将数据类型存储在类中,而是以完全不同的格式存储以节省内存。 在某些情况下,我们需要在应用程序中管理数亿个实例,我们不想为每个实例创建一个完整的类。 假设我们有一个具有2个特征的数据类型:

  • 数量(双精度,8字节)
  • 布尔值(1字节)
尽管我们只需要9个字节来存储这些信息,但将其放入一个类意味着我们至少需要16个字节(因为填充),而对于v指针,我们甚至可能需要24个字节。 对于数亿个实例,每个字节都会计数(我有一个64位的应用程序变体,在某些情况下它需要6GB的内存)

void指针方法的优点是,我们几乎可以对void指针中的任何内容进行编码,并在需要信息时决定如何使用它(将其用作真实指针、索引等),但要以类型安全为代价

模板化的解决方案没有帮助,因为通用逻辑构成了应用程序的很大一部分,我们不想将所有这些模板化。此外,数据模型可以在运行时进行扩展,这也意味着模板没有帮助

有没有比空指针更好(而且更安全)的方法来处理这个问题?
任何与此相关的框架、白皮书、研究材料的引用?

在这种情况下,听起来您应该简单地使用重载。例如:

#ifdef __cplusplus // Only enable this awesome thing for C++:
#   define PROVIDE_OVERLOAD(CLASS,TYPE) \
    inline void ShowNiceDialog(const CLASS& obj){ \ 
         ShowNiceDialog(static_cast<void*>(&obj),TYPE); \
    }

    PROVIDE_OVERLOAD(Car,DATATYPE_CAR)
    PROVIDE_OVERLOAD(Bicycle,DATATYPE_BICYCLE)
    // ...

#undef PROVIDE_OVERLOAD // undefine it so that we don't pollute with macros
#endif // end C++ only 
如果更改了
c
的类型,则它仍将使用适当的重载(如果没有重载,则会给出错误)。这并不能阻止用户使用现有的类型不安全的C变量,但由于类型安全的版本更容易调用,我希望其他开发人员也会喜欢它

编辑
我应该补充一点,上面回答的问题是如何使API类型安全,而不是如何使实现类型安全。这将帮助使用您的系统的用户避免不安全的调用。还请注意,这些包装器为使用编译时已知的类型提供了一种类型安全的方法。。。对于动态类型,确实有必要使用不安全的版本。但是,另一种可能性是您可以提供如下所示的包装器类:

 Car c;
 // ...
 ShowNiceDialog(c);
class DynamicObject
{
    public:
         DynamicObject(void* data, int id) : _datatype_id(id), _datatype_data(data) {}
         // ...
         void showNiceDialog()const{ ShowNiceDialog(_datatype_data,_datatype_id); }
         // ...
    private:
         int _datatype_id;
         void* _datatype_data;
};
对于这些动态类型,在构建对象时仍然没有太多安全性,但一旦构建了对象,就有了更安全的机制。将其与类型安全工厂相结合是合理的,这样API的用户就不会自己实际构造DynamicObject类,因此就不需要调用不安全的构造函数。

我会使用traits

template <class T>
struct DataTypeTraits
{
};

template <>
struct DataTypeTraits<Car>
{
   // put things that describe Car here
   // Example: Give the type a name
   static std::string getTypeName()
   {
      return "Car";
   }
};
template <>
struct DataTypeTraits<Bicycle>
{
   // the same for bicycles
   static std::string getTypeName()
   {
      return "Bicycle";
   }
};

template <class T>
ShowNiceDialog(const T& t)
{
   // Extract details of given object
   std::string typeName(DataTypeTraits<T>::getTypeName());
   // more stuff
}
模板
结构数据类型特征
{
};
模板
结构数据类型特征
{
//把描述汽车的东西放在这里
//示例:为类型指定一个名称
静态std::string getTypeName()
{
归还“汽车”;
}
};
模板
结构数据类型特征
{
//自行车也一样
静态std::string getTypeName()
{
归还“自行车”;
}
};
模板
显示对话框(常量T&T)
{
//提取给定对象的详细信息
std::string typeName(DataTypeTraits::getTypeName());
//更多的东西
}

这样,无论何时添加要应用的新类型,都不需要更改ShowNiceDialog()。您只需要为新类型专门化DataTypeTraits。

如果您不想要一个完整的类,您应该仔细阅读模式。它是为了节省内存而设计的

编辑:抱歉,午餐时间暂停;)

典型的FlyWeight方法是将大量对象共有的特性与给定实例的典型特性分开

一般来说,它意味着:

struct Light
{
  kind_type mKind;
  specific1 m1;
  specific2 m2;
};
kind\u type
通常是指针,但不是必需的。在您的例子中,这将是一个真正的浪费,因为指针本身将是“有用”信息的4倍大

在这里,我认为我们可以利用填充来存储id。毕竟,正如您所说,它将扩展到16位,即使我们只使用其中的9位,所以我们不要浪费其他7位

struct Object
{
  double quantity;
  bool flag;
  unsigned char const id;
};
请注意,元素的顺序很重要:

0x00    0x01    0x02    0x03
[      ][      ][      ][      ]
   quantity       flag     id

0x00    0x01    0x02    0x03
[      ][      ][      ][      ]
   id     flag     quantity

0x00            0x02            0x04
[      ][      ][      ][      ][      ][      ]
   id     --        quantity      flag     --
我不理解“运行时扩展”这一点。看起来很可怕。这是某种自我修改的代码吗

模板允许创建一个非常有趣的FlyWeight形式:

可储存在以下容器中:

typedef std::vector<types_t> vector_t;
第二种方法看起来很奇怪,但它意味着你可以以一种最有趣的方式使用它,作为谓词:

vector_t vec = /**/;
std::foreach(vec.begin(), vec.end(), boost::apply_visitor(DoSomething()));
仔细阅读变体,它是最有趣的

  • 编译时检查:您错过了一个
    操作符()
    ?编译器抛出
  • 不需要RTTI:没有虚拟指针,没有动态类型-->与使用union一样快,但安全性更高

当然,您可以通过定义多个变体来分割代码。如果代码的某些部分只处理4/5类型,那么就为它使用一个特定的变量:)

完全可以在s中更改类的打包
typedef std::vector<types_t> vector_t;
struct DoSomething: boost::static_visitor<>
{
  void operator()(Dog const& dog) const;

  void operator()(Car const& car) const;
  void operator()(Cycle const& cycle) const;
  void operator()(GenericVehicle const& vehicle) const;

  template <class T>
  void operator()(T const&) {}
};
types_t t;
boost::apply_visitor(DoSomething(), t);

// or

boost::apply_visitor(DoSomething())(t);
vector_t vec = /**/;
std::foreach(vec.begin(), vec.end(), boost::apply_visitor(DoSomething()));
class VehicleBase {
public:
    virtual std::string GetCarOwnerFirstName() = 0;
    virtual ~VehicleBase();
};
class Car : public VehicleBase {
    int index;
public:
    std::string GetCarOwnerFirstName() { return GetSingleton().carownerfirstnames[index]; }
};