如何最好地定义/构造/初始化 std::string 包装器类



我在 std::string 周围使用包装类,但是初始化/构造它的最简单/最干净/最完整的方法是什么。我至少需要 3 种方式

  • 从字符串文本
  • 从 std::字符串右值,避免复制!?
  • 从string_view和字符串(是的,复制)

天真的程序员只想将任何构造自动委托给 std::string,但这不是一个功能。

struct SimpleString
{
SimpleString() = default;

template<typename T>
SimpleString( T t ) : Text( t ) { }   // <==== experimental

// Alternative: are these OK
SimpleString( const char* text ) : Text( text ) { }
SimpleString( std::string&& text ) : Text( text ) { }
SimpleString( const std::string_view text ) : Text( text ) { }
std::string Text;
};

先发制人:是的,我想要它,我需要它。用例:调用一个泛型函数,其中 SimpleString 的处理方式与 std::string 不同。

关于从 std::string 继承的注意事项:这可能是一个坏主意,因为隐式转换会在第一时间发生。

如果您的主要目标是字符串转换构造函数,则无需经历任何巨大的障碍。最简单和最好的方法是只接受 API 上的std::string按值并将其std::move到位。

class SimpleString
{
public:
SimpleString() = default;
SimpleString(std::string text) : Text(std::move(text)) { }
// ... other constructors / assignment ...
private:
std::string Text;
};

这样做,我们可以通过以下方式利用std::string已经可以构造的事实:

  • C 字符串/文字,
  • std::string_view
  • 其他std::string对象(左值或右值),以及
  • 任何可能已可转换为std::string的用户定义类型

这样做还使用户现在可以选择通过移动其当前std::string对象来构造SimpleString,还是允许SimpleString显式复制它。这为调用方提供了更大的灵活性。

这很好用,因为无论如何您已经拥有此std::string,因此按值接受它并std::move对象是"坐"此值的简单方法。移动std::string相当便宜,相当于指针、大小和分配器的一些分配 - 因此这会产生可以忽略不计的性能开销,同时也是一种简单且可维护的方法。


现代C++和移动语义使得接受这些类型变得如此容易。你绝对可以去重载所有N种构造它的方式(const char*std::string_viewT可转换为字符串等);但是,按原样接受类型并移动它会简单得多。这是最灵活的方法,同时也保持其简单性和可维护性。


以下是一些使用模板与使用std::string+std::move进行比较的基准。一般来说,保持简单要好得多。你的同事和未来的维护者会感谢你(即使未来的维护者是你,一年后)。

std::string有很多构造函数,必须在类中重新实现所有这些构造函数会很麻烦。这就是为什么你最适合转发给std::string的构造函数,如下所示:

#include <utility>
struct SimpleString
{
template <typename... Args>
SimpleString(Args &&... args) : Text(std::forward<Args>(args)...)
{
}
std::string Text;
};

为了避免与默认和复制/移动构造函数发生冲突,我们需要使用 SFINAE:

template <typename... Args>
constexpr bool is_empty_pack_v = sizeof...(Args) == 0;
template <typename T, typename... Args>
constexpr bool is_copy_or_move_pack_v = sizeof...(Args) == 1 &&
(... && std::is_same_v<T, std::remove_reference_t<Args>>);
struct SimpleString
{
SimpleString() = default;
SimpleString(const SimpleString &) = default;
SimpleString(SimpleString &&) = default;
template <typename... Args, std::enable_if_t<
not is_empty_pack_v<Args...> &&
not is_copy_or_move_pack_v<SimpleString, Args...> &&
std::is_constructible<std::string, Args...>, int> = 0>
SimpleString(Args &&... args) : Text(std::forward<Args>(args)...)
{
}
std::string Text;
};

相关内容

  • 没有找到相关文章

最新更新