泛型/模板编程最佳做法:限制类型,或不限制类型
Generic/template programming best practices: To limit types, or not to limit types
这是我的问题。 我只是好奇关于限制可以传递给泛型函数或类的类型的共识是什么。 我以为我在某个时候读过,如果你在做泛型编程,通常最好让事情保持开放,而不是试图关闭它们(不记得来源)。
我正在编写一个具有一些内部泛型函数的库,我觉得它们应该只允许库中的类型与它们一起使用,仅仅是因为这就是我的意思。 另一方面,我不确定我锁定事情的努力是否值得。
有人可能有一些关于这个话题的统计数据或权威评论的来源吗? 我也对合理的意见感兴趣。 希望这不会完全使这个问题无效:\
另外,SO 上是否有任何等同于"最佳实践"的标签? 我没有具体看到那个,但似乎能够为给定的 SO 主题提供所有最佳实践信息会有所帮助......也许不是,只是一个想法。
编辑:到目前为止,一个答案提到我正在做的库类型将很重要。 它是一个数据库库,最终可以使用STL容器,可变参数(元组),Boost Fusion等性质的东西。 我可以看到这将如何相关,但我也会对确定走哪条路的经验法则感兴趣。
始终保持尽可能打开 状态 - 但请确保
- 记录要与泛型代码一起使用的有效类型所需的接口和行为。
- 使用类型的接口特征(特征)来确定是允许/禁止它。不要根据类型名称做出决定。
- 产生合理的诊断,如果有人使用了错误的类型。C++模板非常适合筹集吨数深度嵌套的错误(如果它们被实例化)错误的类型 - 使用类型特征、静态断言和相关技术,很容易产生更简洁的错误消息。
在我的数据库框架中,我决定放弃模板并使用单个基类。 泛型编程意味着可以使用任何或所有对象。 特定类型类的重要性超过了少数泛型操作。 例如,可以比较字符串和数字是否相等;BLOB(二进制大型 OBjects)可能希望使用不同的方法(例如比较存储在不同记录中的 MD5 校验和)。
此外,字符串和数值类型之间还有一个继承分支。
通过使用继承层次结构,我可以使用 Field
类或专用类(如 Field_Int
)引用任何字段。
STL最强大的卖点之一是它是如此开放,它的算法与我的数据结构以及它提供给自己的数据结构一起工作,我的算法与它的数据结构和我的数据结构一起工作。
让你的算法对所有类型开放还是将它们限制在你的类型中是否有意义,很大程度上取决于你正在编写的库,我们对此一无所知。
(最初我的意思是回答说,狡猾的开放是泛型编程的全部内容,但现在我看到泛型总是有限制的,你必须在某个地方划清界限。如果这有意义的话,它也可能仅限于您的类型。
至少 IMO,正确的做法大致是概念尝试的内容:与其尝试验证您正在接收指定的类型(或一组指定类型之一),不如尽力指定对类型的要求,并验证您收到的类型是否具有正确的特征,并且可以满足模板的要求。
与概念非常相似,这样做的大部分动机是在未满足这些要求时简单地提供良好、有用的错误消息。最终,如果有人尝试在不符合其要求的类型上实例化模板,编译器将生成错误消息。问题是,除非您采取措施确保它是,否则错误消息不会很有帮助。
问题
如果您的客户端可以在公共标头中看到您的内部函数,并且如果这些内部泛型函数的名称是"通用的",那么您可能会使您的客户端面临意外调用内部泛型函数的风险。
例如:
namespace Database
{
// internal API, not documented
template <class DatabaseItem>
void
store(DatabaseItem);
{
// ...
}
struct SomeDataBaseType {};
} // Database
namespace ClientCode
{
template <class T, class U>
struct base
{
};
// external API, documented
template <class T, class U>
void
store(base<T, U>)
{
// ...
}
template <class T, class U>
struct derived
: public base<T, U>
{
};
} // ClientCode
int main()
{
ClientCode::derived<int, Database::SomeDataBaseType> d;
store(d); // intended ClientCode::store
}
在这个例子中,main
的作者甚至不知道 Database::store 的存在。 他打算调用 ClientCode::store,然后变得懒惰,让 ADL 选择函数而不是指定ClientCode::store
。 毕竟,他对store
的论点来自与store
相同的命名空间,因此它应该可以正常工作。
它不起作用。 此示例调用 Database::store
。 根据Database::store
的内部,此调用可能会导致编译时错误,或者更糟的是,运行时错误。
如何修复
您命名函数的通用性越高,发生这种情况的可能性就越大。 为您的内部函数(必须出现在标头中的函数)提供真正的非通用名称。 或者将它们放在像 details
这样的子命名空间中。 在后一种情况下,必须确保客户端永远不会将details
作为 ADL 的关联命名空间。 这通常是通过不创建客户端将在 namespace details
中直接或间接使用的类型来实现的。
如果你想变得更偏执,那就开始用enable_if
锁定事情。
如果您认为您的内部函数可能对您的客户有用,那么它们就不再是内部函数了。
上面的示例代码并不牵强。 它发生在我身上。 它已经发生在namespace std
的功能上。 我称store
在这个例子中过于通用。 std::advance
和 std::distance
是过于通用的代码的典型示例。 这是需要防范的。 这是一个概念试图解决的问题。
- C++模板编程设计问题 - 根据输入文件返回不同的类型
- 在模板元编程中使用列表类型
- 使用元编程选择 int 类型,将生成错误
- 元编程构造,它返回枚举的基础类型和迭代器的整数
- 在泛型编程中选择类型参数
- 使用模板元编程表示一组类型C++
- 声明基类类型的指针,但随后通过指向子类来实例化它.这是良好的编程实践吗?
- C++模板元编程:如何在表达式模式中推断类型
- C++14 元编程:在编译/初始化时自动构建类型列表
- 是否可以使用元编程将类型列表转换为对列表中每种类型具有特定隐式转换行为的新类型?
- 如何使用模板元编程在 C++17 中将一种类型转换为另一种类型
- 通过编程复制LLVM IR类型错误
- C++ 模板元编程:如何创建和迭代模板类中"typedefs"的类型列表。
- C++模板元编程:模板类型上的编译时条件运算符
- 错误:无法分配抽象类型的对象 'mySynth' VST 编程
- 为什么"char"结构类型中的编程实践不好?
- 模板元编程:为什么平面类型是失败的
- C++模板元编程静态类型检查
- 开始使用p2p类型的网络/编程
- 模板元编程:(特征?)将指定的模板剖析为类型T<T2,T3 N,T4,...>