指向模板类的指针

时间:2013-03-08 17:59:12

标签: c++ templates pointers

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

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

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

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

2 个答案:

答案 0 :(得分:2)

是。这是不安全的。每当你施放void*时,你就冒了UB的风险。编译器不会向您发出警告,因为它不再具有执行此操作所需的类型信息。因此,你要投射到你没有做的正确类型。

从技术上讲,你在这里造成了未定义的行为。然而,我的赌注是它通常会起作用。这与你在C中必须做的一些蠢事没有什么不同。

它的工作原理是你的实例的二进制布局可能是相同的。首先是'n',如果你正在做这个讨厌的技巧,你需要拥有它,然后是数组的开头。

如果你在指针范围之外做到这一点,那么你就会搞砸自己。

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

编辑:

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

答案 1 :(得分:0)

你所拥有的是在编译时解析的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文件中,需要看到它们的使用位置。