转换指向模板类的指针

Cast of pointers to template classes

本文关键字:指针 转换      更新时间:2023-10-16

为了对接口用户隐藏实现细节并避免模板化函数的广泛使用,我想到了以下概念:

//data.h

#ifndef DATA_H_
#define DATA_H_
#include <cstddef>
template <size_t N = 0>
class Data
{
   public:
      const size_t n;
      size_t values[N];
      Data<N>();
};
#endif // DATA_H_
//data.cpp

#include "data.h"
template <size_t N> Data<N>::Data()
:
   n(N),
   values()
{
   for ( size_t i = 0; i < n; ++i )
   {
      values[i] = i;
   }
}
template class Data<1u>;
template class Data<2u>;
//list.h

#ifndef LIST_H_
#define LIST_H_
#include <cstddef>
#include <memory>
class List
{
   private:
      std::shared_ptr<void> data;
   public:
      List(const size_t);
      void printData() const;
};
#endif // LIST_H_
//list.cpp

#include "list.h"
#include <iostream>
#include <stdexcept>
#include "data.h"
List::List(const size_t n)
:
   data()
{
   switch ( n )
   {
      case 1u:
         data = std::static_pointer_cast<void>(std::make_shared<Data<1u>>());
         break;
      case 2u:
         data = std::static_pointer_cast<void>(std::make_shared<Data<2u>>());
         break;
      default:
         throw std::runtime_error("not instantiated..");
   }
}
void List::printData() const
{
   auto obj = std::static_pointer_cast<Data<>>(data);  // my question is about this
   std::cout << obj->n << ": ";
   for ( size_t i = 0; i < obj->n; ++i )
   {
      std::cout << obj->values[i] << " ";
   }
   std::cout << "n";
}
//main.cpp

#include "list.h"
int main()
{
    for ( size_t i = 1; i <= 2; ++i )
    {
       try
       {
          List list(i);
          list.printData();
       }
       catch ( ... )
       {
          return 1;
       }
    }
}

我知道有些人可能会认为这是一个可怕的设计。请不要在这里讨论这个,除非你有一个绝妙的选择。

我的问题是关于List::printData()中的auto obj = std::static_pointer_cast<Data<>>(data);行。感觉有点不安全。是否有保证使用了正确的实例化?g++-4.6.3不给这个代码一个警告,它打印预期的值

是。它是不安全的。任何时候你通过一个void*铸造你有UB的风险。编译器不会警告您,因为它不再具有这样做所需的类型信息。因此,转换为正确的类型是您的责任,而您并没有这样做。

从技术上讲,你在这里导致了未定义的行为。不过,我敢打赌,这通常会奏效。这与你在C语言中一直要做的一些做作的废话没什么不同。

它能够工作的原因是实例的二进制布局可能是相同的。首先是'n',如果你要做这个讨厌的把戏,你确实需要它,然后是数组的开头。

如果你在指针领域之外这样做,那你就完蛋了。

对象被正确删除的唯一原因是shared_ptr在创建时创建了一个默认的删除器,因此它知道如何删除正确的类型。如果你尝试这个,任何其他智能指针都会引起各种各样的废话。

编辑:

现在,一个更好的方法是放弃使用类型系统来调整数组的大小。如果你真的想要一个运行时分配的数组,使用运行时系统来创建它!无论如何,你是在免费商店中创建它的,所以你不会从像这样滥用类型系统中得到任何好处。如果你只是根据传递给列表构造函数的大小来分配数组,你就可以拥有安全的、可预测的、标准的行为。

这里有一个在编译时解析的static_cast。所以你告诉编译器的是:

auto obj = std::static_pointer_cast<Data<>>(data); 

static_pointer_cast将变量 data 改为类型 std::shared_ptr<Data<>>
默认情况下(正如您在模板原型中声明的那样),Data<>表示Data<0>

所以你总是会得到相同类型的shared_pointer

你能做的就是做一个接口,然后在运行时得到它的大小。

class IData 
{
  virutal size_t GetDataSize() = 0;
}
template <size_t N = 0>
class Data : public IData
{
   public:
      const size_t n;
      size_t values[N];
      Data<N>();
      virtual size_t GetDataSize() override { return N; }
};

然后保持一个列表的接口类型,只使用data->GetDataSize();

此外,不要把模板实现放在.cpp文件中,它们需要在使用它们的地方被看到。