0

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

// 数据.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_

// 数据.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>;

// 列表.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_

// 列表.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不会对此代码发出警告,它会打印预期值。

4

2 回答 2

2

是的。这是不安全的。任何时候你通过 a 施放void*你都会冒着 UB 的风险。编译器不会警告您,因为它不再具有执行此操作所需的类型信息。因此,您需要转换为正确的类型,而您没有这样做。

从技术上讲,您在这里造成了未定义的行为。但是,我敢打赌,它通常会起作用。这与您一直在 C 中必须做的一些胡说八道没有什么不同。

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

如果您曾经在指针领域之外这样做,那么您将自己搞砸。

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

编辑:

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

于 2013-03-08T18:13:16.943 回答
0

您所拥有的是在编译时解析的 static_cast 。所以你告诉编译器的是:

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

static_pointer_castdata要键入的变量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 文件中,需要看到它们的使用位置。

于 2013-03-08T18:16:11.757 回答