【问题标题】:Handle std::basic_string<> with different type arguments使用不同类型参数处理 std::basic_string<>
【发布时间】:2010-10-17 17:11:20
【问题描述】:

我想实现一个 c++ 库,并且像许多其他库一样,我需要从用户那里获取字符串参数并返回字符串。当前标准定义了 std::string 和 std::wstring(我更喜欢 wstring)。理论上我必须两次使用字符串参数实现方法:

虚拟 void foo(std::string &) = 0; // 在内部从先前定义的字符集转换为 unicode 虚拟 void foo(std::wstring &) = 0;

C++0x 并没有让生活更轻松,因为我需要 char16_t 和 char32_t:

虚拟 void foo(std::u16string &) = 0; 虚拟 void foo(std::u32string &) = 0;

在内部处理这些不同的类型 - 例如将所有类型放入私有向量成员 - 需要转换、包装器......这太可怕了。

另一个问题是,如果用户(或我自己)想要使用自定义分配器或自定义特征类:一切都会产生全新的类型。例如,要为多字节字符集编写自定义 codecvt 特化,标准说我必须引入一个自定义 state_type - 它需要一个自定义 trait 类,这会产生一个新的 std::basic_ifstream 类型 - 这与期望 std 的接口完全不兼容::ifstream& 作为参数。

一种可能的解决方案是将每个库类构建为一个模板,用于管理用户指定的 value_type、traits 和分配器。但这太过分了,并且使抽象基类(接口)变得不可能。

另一种解决方案是只指定 一个 类型(例如 u32string)作为默认值,每个用户都必须使用这种类型传递数据。但是现在考虑一个使用 3 个库的项目,第一个库使用 u32string,第二个库使用 u16string,第三个库 wstring -> HELL。

我真正想要的是将方法声明为 void foo(put_unicode_string_here) - 不引入我自己的 UnicodeString 或 UnicodeStream 类。

【问题讨论】:

  • 在您的应用程序中,您应该始终使用 ONE 字符串类(每个都应该使用该类型(上述类型之一))。这是您的内部字符串表示形式。应用程序的接口可能需要多种不同类型的输入,但此输入总是在传递出接口层之前转换为内部字符串表示形式。
  • 我不确定您所指的与编解码器有关的内容。编写它们似乎相对简单。有关简单的使用模式,请参见此处:stackoverflow.com/questions/207662/…
  • 如果你必须在你的 codecvt 实现中使用 state_type(在你的例子中是 mbstate_t),你会看到你不能使用 mbstate_t 因为它的实现只有你的 c++ 的开发者知道stl。您需要引入自己的 state_type,然后专门为 编写 codecvt。这就是标准告诉您自定义编解码器的内容。 stroustrups 书中描述了从 std::codecvt 派生,但未涵盖必须使用 state_type 的情况。
  • @martin 作为对您的第一条评论的回应:问题是从输入到内部表示的转换。如何在 basic_string 和 std::wstring 之间轻松转换?如何在 basic_ifstream 和 std::wifstream 之间进行转换?请记住,某些方法可能需要对 std::wstring & Co 的引用。这意味着用户(或 lib 开发人员,如果在内部完成转换)必须管理原始对象和转换后的对象(传递给 lib )。
  • @cytrinox:我还是不明白 codecvt 有什么问题。正如我所说,即使您使用状态类型,它也相对简单。也许最好你问一个关于这个主题的问题,这样你就可以得到正确实施它的建议。

标签: c++ c++11


【解决方案1】:

如果您不想支持所有内容,总要做出选择,但我个人认为将输入限制为 UTF-8 是最简单的。只需使用普通的旧std::string,每个人都很高兴。在实践中,(您的库的)用户只需要在 Windows 上转换为 UTF-8,但有很多方法可以完成这个简单的任务。

更新:另一方面,您可以对所有代码进行模板化,并将std::basic_string&lt;T&gt; 作为整个代码中的模板。如果您根据模板参数的大小执行不同的操作,这只会变得混乱。

【讨论】:

  • 第一个解决方案无法处理自定义特征或分配器。第二个完全是矫枉过正。
  • 查看@Martin 的回复,他一针见血……你不能满足每一种可能的字符串变化类型,只要坚持标准,不据我所知,用户会关心你使用什么内部表示......
  • “问题”不是指定一种内部表示。问题是从各种 basic_strings 和 basic_streams 转换为内部表示。如何设计/编写一个接口,它接受带有各种参数的模板并将它们在内部转换为内部表示?
  • @cytronix:接受具有各种分配器、特征和字节大小的各种字符串会很麻烦,恕我直言,应该留给图书馆的用户。
  • 好的,用户如何轻松做到这一点?假设我定义了一个需要 std::wstreambuf* 作为参数并将一些数据放入 - 并且用户有一个 basic_streambuf 对象的方法?
【解决方案2】:

char_traits 确实是一个非常糟糕的随机特征垃圾箱。每个字符串是否应该预先指定编码机制本身的最大支持文件大小、区分大小写和(呃)状态类型?没有。

但是,即使具有精心设计的特征,您所要求的也是不可能的。 stringwstring 是有意义的不同,因为内部字符类型的大小不同。要运行任何类型的算法,您需要在对象中查询char_t。这需要 RTTI 或虚函数,因为 basic_string 不会(也不应该)在运行时维护该信息。

一种可能的解决方案是将每个库类构建为一个模板,用于管理用户指定的 value_type、traits 和分配器。但这太过分了,并且使抽象基类(接口)变得不可能。

这是唯一完整的解决方案。模板实际上确实与抽象基类配合得很好:许多模板可以派生自非模板抽象基类,或者基类也可以被模板化。然而,由于编写完美通用代码的敏感性和乏味,即使不是站不住脚也是很困难的。

另一种解决方案是只指定一种类型(例如 u32string)作为默认值,每个用户都必须使用这种类型传递数据。但是现在考虑一个使用 3 个库的项目,第一个库使用 u32string,第二个库使用 u16string,第三个库 wstring -> HELL。

这就是为什么我对 C++11 的“改进”Unicode 支持感到害怕。它简化了与文件数据的直接交互,不鼓励抽象为 common wchar_t 内部格式。最好要求 UTF-16 和 UTF-32 的特定编解码器并指定 wchar_t 必须至少为 21 位。以前在干净的 C++ 接口中只有“愚蠢的”char 和“聪明的”wchar_t 库,我们可能不得不应对额外的宽度——而char16_t 只是一个即时的危险信号。

但是,那是在路上。

如果您真的最终使用了许多不兼容的库,并且问题是在需要不同格式的函数之间穿梭数据,那么请编写一个ScopeGuard-style 实用程序来转换您选择的通用格式,例如wstring .此实用程序可以是一个模板,它对您需要的每种不兼容格式都有明确的特化,也可以是一组非模板化的类。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多