使用'include wrapper'是否被认为是不良做法/表示设计不佳?

Is using an 'include wrapper' considered bad practice/indicative of bad design?

本文关键字:表示 不良 wrapper include 是否 使用 认为是      更新时间:2023-10-16

考虑这样一种情况:您的任务是为标准库容器编写一个简单漂亮的打印工具。在标题pretty_print.hpp中,您声明以下函数:

// In pretty_print.hpp
template<typename T>
void pretty_print(std::ostream& os, std::vector<T> const& vec);
template<typename T>
void pretty_print(std::ostream& os, std::set<T> const& set);
template<typename T, typename U>
void pretty_print(std::ostream& os, std::map<T, U> const& map);
// etc.

但是,由于容器不能正向声明,因此必须为每个容器标头#include。因此,将pretty_print.hpp包含到库的其他部分会(可能?)导致相当多的代码膨胀。因此,为了避免将这些依赖项引入其他编译单元,您制作了一堆名为print_vector.hppprint_set.hpp等的文件(我称它们为"头包装器",因为我找不到任何其他术语),它们都有类似的布局:

// In print_vector.hpp
#include <vector>
template<typename T>
void pretty_print(std::ostream& os, std::vector<T> const& vec);
// In print_set.hpp
#include <set>
template<typename T>
void pretty_print(std::ostream& os, std::set<T> const& set);
// you get the point

因此,当你想将pretty_print作为向量时,你需要#include print_vector.hpp,它只会将<vector>引入当前编译单元,而不是<set><map>或任何其他你可能不需要的头。请注意,我使用pretty_print作为示例(我相信有比漂亮的打印容器优越得多的方法),但您可能还想这样做还有其他原因(例如,在包含windows.h之前,在#define WIN32_LEAN_AND_MEAN的位置制作lean_windows.h标头"包装器")。

我看不出这种方法有什么错,因为它意味着你可以避免在编译单元中引入一堆你可能不使用/不需要的头导致的潜在膨胀。尽管如此,它仍然感觉"错误",因为对其他人来说,你的"包含包装器"实际上包含了你想要的头,并且似乎玷污了包含标准库头的"神圣性"(#include <string>是惯用的,而#include "string_wrapper.hpp"不是)。

这种做法是否被认为是不良设计的表现?

一些库处理这类事情的一种方法是让用户决定。制作print/vector.hppprint/set.hpp这样的文件,也制作print/all.hpp这样的文件(或者只是print.hpp,尽管这可能会助长坏习惯)。最后一个文件只是#包括了所有单独的文件,所以想要"方便"的人可以拥有它,而那些想要精简编译的人也可以拥有它。

Boost的智能指针库是一个与上面类似的常见示例:http://www.boost.org/doc/libs/release/boost/smart_ptr.hpp

我宁愿问,这是否真的有必要。你慢慢地被吸引到一类一头的方向,这会把你的#include部分变成一个痛苦的混乱。

如果您想确保不会将一个标识符与另一个标识符混淆,只需使用名称空间即可。

namespace PrettyPrint
{
    template<typename T>
    void pretty_print(std::ostream& os, std::vector<T> const& vec);
    template<typename T>
    void pretty_print(std::ostream& os, std::set<T> const& set);
    template<typename T, typename U>
    void pretty_print(std::ostream& os, std::map<T, U> const& map);
}

现在,这些函数得到了保护,不会被其他函数误认,并且您的代码仍然安全优雅。

我看到的唯一缺点是,包含长标头会使编译过程稍微长一点。然而,我认为,我们谈论的最多是十分之几秒,所以如果你不在处理像旧386这样的东西,那应该不是什么大问题(更不用说预编译的头了,但老实说,我甚至一次也没有使用过它们)。

您可以简单地尝试一个更通用的版本:

template<class T>
void print_element(std::ostream& os, T const& element)
{
  os << T;
}
template<class Key,class Value>
void print_element(std::ostream& os, std::pair<Key,Value> const& element)
{
  os << '(' << element->first << ',' << element->second << ')';
}
template<typename Container>
void pretty_print(std::ostream& os, Container const& c)
{
   for (auto i: c)
   {
     // print some stuff
     print_element(os, *i);
     // print other stuff
   }
}