为了隐藏界面用户的实现细节并避免广泛使用模板化函数,我想到了以下概念:
// 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
未对此代码发出警告,并打印出预期值。
答案 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文件中,需要看到它们的使用位置。